TSK-1215: Workbasket component NGXS store (#1169)

* TSK-1214: refactored taskana-classification

TSK-1214 Trying to make drag'n drop in tree possible

TSK-1214 Removed refreshClassification output from tree

TSK-1214 New action in store updates a classification and refetches all, saving now correctly refreshes the classification-list

TSK-1214 Fixed tests in tree component

TSK-1214 Removed tree service and corresponding test in class-details

TSK-1214 fixed issues in tree where multiple actions to store are fired incorrectly

TSK-1214 added accessibility action, use space to select a tree node

TSK-1214 swapped space and enter in tree component, cleaned code

TSK-1214 fixed bug where page isn't updated dynamically according to browser path

TSK-1214 workaround circular dependency.

service uses snapshot of store, does not actually access the state in store

TSK-1214 fixed eslint. TODO: circular dependency between classification.service and classification.state

TSK-1214 changed first() to take(1) to fix Observable dying during test

TSK-1214 fixed test cases and lint issues

TSK-1214 fixed circular dependency

TSK-1214 devmode = false

TSK-1214: fixed merge problems with notificationService and removed some warnings

TSK-1214: fixed merge problems with notificationService

TSK-1214 remove wrong imports from before merge

* TSK-1215 initialized workbasket ngxs store with get all and select workbasket

* TSK-1215 added actions for creating workbasket and getting workbasket access items

* TSK-1215 fixed models' attributes in workbasket

* TSK-1215 changed workbasket models from class type to interface type

* TSK-1215 correct the naming scheme for models in workbasket

* TSK-1215 update workbasket models to mirror backend entities

* TSK-1215 implemented workbasketsSummary in store

* TSK-1215 workbasket list data is now populated using NGXS Store

* TSK-1215 fixed console errors while navigating pagination

* TSK-1215 initialized workbasket-overview

* TSK-1215 removed unnecessary subscriptions

* TSK-1215 fixed workbasket access items still using WorkbasketAccessItems as a class instead of an interface

* TSK-1215 implemented routing for workbasket overview, removed master-details component

* TSK-1215 implemented new behaviors based on ngxs store instead of router

* TSK-1215 bug fixes for creating, selecting and deselecting workbasket

* TSK-1215 further bugs fixing regarding creating and selecting workbasket

* TSK-1215 fixed bugs where input workbasket was immutable in information component

* TSK-1215 added remove distribution target and delete workbasket actions

* TSK-1215 implemented mark workbasket for deletion in state

* TSK-1215 implemented workbasket copy function

* TSK-1215 implemented mark workbasket for deletion

* TSK-1287 implemented typeahead for owner field in workbasket

* TSK-1215 added actions for workbasket access items and distribution targets

* TSK-1215 fixed bugs saving or creating new workbasket

* TSK-1215 finished workbasket information

* TSK-1215 updated tests for workbasket list, details and information

* TSK-1215 increased HTML visibility, refine css

* TSK-1215 implemented workbasket access items using ngxs state

* TSK-1215 increased workbasket distribution target HTML code visibility

* TSK-1215 initialization of distribution targets now uses actions from state

* TSK-1215 use takeUntil for subscriptions instead of manually unsubscribing

* TSK-1215 save function for distribution targets now dispatch an action

* TSK-1215: code refinement in ngxs store

* TSK-1214: refactored taskana-classification

TSK-1214 Trying to make drag'n drop in tree possible

TSK-1214 Removed refreshClassification output from tree

TSK-1214 New action in store updates a classification and refetches all, saving now correctly refreshes the classification-list

TSK-1214 Fixed tests in tree component

TSK-1214 Removed tree service and corresponding test in class-details

TSK-1214 fixed issues in tree where multiple actions to store are fired incorrectly

TSK-1214 added accessibility action, use space to select a tree node

TSK-1214 swapped space and enter in tree component, cleaned code

TSK-1214 fixed bug where page isn't updated dynamically according to browser path

TSK-1214 workaround circular dependency.

service uses snapshot of store, does not actually access the state in store

TSK-1214 fixed eslint. TODO: circular dependency between classification.service and classification.state

TSK-1214 changed first() to take(1) to fix Observable dying during test

TSK-1214 fixed test cases and lint issues

TSK-1214 fixed circular dependency

TSK-1214 devmode = false

TSK-1214: fixed merge problems with notificationService and removed some warnings

TSK-1214: fixed merge problems with notificationService

TSK-1214 remove wrong imports from before merge

* TSK-1215 initialized workbasket ngxs store with get all and select workbasket

* TSK-1215 added actions for creating workbasket and getting workbasket access items

* TSK-1215 fixed models' attributes in workbasket

* TSK-1215 changed workbasket models from class type to interface type

* TSK-1215 correct the naming scheme for models in workbasket

* TSK-1215 update workbasket models to mirror backend entities

* TSK-1215 implemented workbasketsSummary in store

* TSK-1215 workbasket list data is now populated using NGXS Store

* TSK-1215 fixed console errors while navigating pagination

* TSK-1215 initialized workbasket-overview

* TSK-1215 removed unnecessary subscriptions

* TSK-1215 fixed workbasket access items still using WorkbasketAccessItems as a class instead of an interface

* TSK-1215 implemented routing for workbasket overview, removed master-details component

* TSK-1215 implemented new behaviors based on ngxs store instead of router

* TSK-1215 bug fixes for creating, selecting and deselecting workbasket

* TSK-1215 further bugs fixing regarding creating and selecting workbasket

* TSK-1215 fixed bugs where input workbasket was immutable in information component

* TSK-1215 added remove distribution target and delete workbasket actions

* TSK-1215 implemented mark workbasket for deletion in state

* TSK-1215 implemented workbasket copy function

* TSK-1215 implemented mark workbasket for deletion

* TSK-1287 implemented typeahead for owner field in workbasket

* TSK-1215 added actions for workbasket access items and distribution targets

* TSK-1215 fixed bugs saving or creating new workbasket

* TSK-1215 finished workbasket information

* TSK-1215 updated tests for workbasket list, details and information

* TSK-1215 increased HTML visibility, refine css

* TSK-1215 implemented workbasket access items using ngxs state

* TSK-1215 increased workbasket distribution target HTML code visibility

* TSK-1215 initialization of distribution targets now uses actions from state

* TSK-1215 use takeUntil for subscriptions instead of manually unsubscribing

* TSK-1215 save function for distribution targets now dispatch an action

* TSK-1215: code refinement in ngxs store

* TSK-1215: Optimize select workbasket algorithm

* TSK-1215: Fixed workbasket information not displaying correctly after saving

* TSK-1215: configured tests for workbasket distribution targets

* TSK-1215: updated unit tests for workbasket

* TSK-1215: cleaned up code, prep for PR

* TSK-1215: disable dev mode

* TSK-1215: fixed failed tests occured after merge

* TSK-1215: reverted node version

* TSK-1215: Further bugs fixed due to merging

* TSK-1068: Fixed bugs workbasket staying active after being moved

* TSK-1215: Fixed minor bugs in tests

* TSK-1215: various improvement in code, typeahead style

* TSK-1215: Updated typeahead css

* TSK-1215: Fixed failed tests due to merge

* TSK-1342: getTasksWorkbasketReport now filters for states

* TSK-1214: refactored taskana-classification

TSK-1214 Trying to make drag'n drop in tree possible

TSK-1214 Removed refreshClassification output from tree

TSK-1214 New action in store updates a classification and refetches all, saving now correctly refreshes the classification-list

TSK-1214 Fixed tests in tree component

TSK-1214 Removed tree service and corresponding test in class-details

TSK-1214 fixed issues in tree where multiple actions to store are fired incorrectly

TSK-1214 added accessibility action, use space to select a tree node

TSK-1214 swapped space and enter in tree component, cleaned code

TSK-1214 fixed bug where page isn't updated dynamically according to browser path

TSK-1214 workaround circular dependency.

service uses snapshot of store, does not actually access the state in store

TSK-1214 fixed eslint. TODO: circular dependency between classification.service and classification.state

TSK-1214 changed first() to take(1) to fix Observable dying during test

TSK-1214 fixed test cases and lint issues

TSK-1214 fixed circular dependency

TSK-1214 devmode = false

TSK-1214: fixed merge problems with notificationService and removed some warnings

TSK-1214: fixed merge problems with notificationService

TSK-1214 remove wrong imports from before merge

* TSK-1215 initialized workbasket ngxs store with get all and select workbasket

* TSK-1215 added actions for creating workbasket and getting workbasket access items

* TSK-1215 fixed models' attributes in workbasket

* TSK-1215 changed workbasket models from class type to interface type

* TSK-1215 correct the naming scheme for models in workbasket

* TSK-1215 update workbasket models to mirror backend entities

* TSK-1215 implemented workbasketsSummary in store

* TSK-1215 workbasket list data is now populated using NGXS Store

* TSK-1215 fixed console errors while navigating pagination

* TSK-1215 initialized workbasket-overview

* TSK-1215 removed unnecessary subscriptions

* TSK-1215 fixed workbasket access items still using WorkbasketAccessItems as a class instead of an interface

* TSK-1215 implemented routing for workbasket overview, removed master-details component

* TSK-1215 implemented new behaviors based on ngxs store instead of router

* TSK-1215 bug fixes for creating, selecting and deselecting workbasket

* TSK-1215 further bugs fixing regarding creating and selecting workbasket

* TSK-1215 fixed bugs where input workbasket was immutable in information component

* TSK-1215 added remove distribution target and delete workbasket actions

* TSK-1215 implemented mark workbasket for deletion in state

* TSK-1215 implemented workbasket copy function

* TSK-1215 implemented mark workbasket for deletion

* TSK-1287 implemented typeahead for owner field in workbasket

* TSK-1215 added actions for workbasket access items and distribution targets

* TSK-1215 fixed bugs saving or creating new workbasket

* TSK-1215 finished workbasket information

* TSK-1215 updated tests for workbasket list, details and information

* TSK-1215 increased HTML visibility, refine css

* TSK-1215 implemented workbasket access items using ngxs state

* TSK-1215 increased workbasket distribution target HTML code visibility

* TSK-1215 initialization of distribution targets now uses actions from state

* TSK-1215 use takeUntil for subscriptions instead of manually unsubscribing

* TSK-1215 save function for distribution targets now dispatch an action

* TSK-1215: code refinement in ngxs store

* TSK-1215 initialized workbasket ngxs store with get all and select workbasket

* TSK-1215 added actions for creating workbasket and getting workbasket access items

* TSK-1215 fixed models' attributes in workbasket

* TSK-1215 changed workbasket models from class type to interface type

* TSK-1215 correct the naming scheme for models in workbasket

* TSK-1215 update workbasket models to mirror backend entities

* TSK-1215 implemented workbasketsSummary in store

* TSK-1215 workbasket list data is now populated using NGXS Store

* TSK-1215 fixed console errors while navigating pagination

* TSK-1215 initialized workbasket-overview

* TSK-1215 removed unnecessary subscriptions

* TSK-1215 fixed workbasket access items still using WorkbasketAccessItems as a class instead of an interface

* TSK-1215 implemented routing for workbasket overview, removed master-details component

* TSK-1215 implemented new behaviors based on ngxs store instead of router

* TSK-1215 bug fixes for creating, selecting and deselecting workbasket

* TSK-1215 further bugs fixing regarding creating and selecting workbasket

* TSK-1215 fixed bugs where input workbasket was immutable in information component

* TSK-1215 added remove distribution target and delete workbasket actions

* TSK-1215 implemented mark workbasket for deletion in state

* TSK-1215 implemented workbasket copy function

* TSK-1215 implemented mark workbasket for deletion

* TSK-1287 implemented typeahead for owner field in workbasket

* TSK-1215 added actions for workbasket access items and distribution targets

* TSK-1215 fixed bugs saving or creating new workbasket

* TSK-1215 finished workbasket information

* TSK-1215 updated tests for workbasket list, details and information

* TSK-1215 increased HTML visibility, refine css

* TSK-1215 implemented workbasket access items using ngxs state

* TSK-1215 increased workbasket distribution target HTML code visibility

* TSK-1215 initialization of distribution targets now uses actions from state

* TSK-1215 use takeUntil for subscriptions instead of manually unsubscribing

* TSK-1215 save function for distribution targets now dispatch an action

* TSK-1215: code refinement in ngxs store

* TSK-1215: Optimize select workbasket algorithm

* TSK-1215: Fixed workbasket information not displaying correctly after saving

* TSK-1215: configured tests for workbasket distribution targets

* TSK-1215: updated unit tests for workbasket

* TSK-1215: cleaned up code, prep for PR

* TSK-1215: disable dev mode

* TSK-1215: fixed failed tests occured after merge

* TSK-1215: reverted node version

* TSK-1215: Further bugs fixed due to merging

* TSK-1068: Fixed bugs workbasket staying active after being moved

* TSK-1215: Fixed minor bugs in tests

* TSK-1215: various improvement in code, typeahead style

* TSK-1215: Updated typeahead css

* TSK-1215: Fixed failed tests due to merge

* TSK-1215: Rebase, prep for merge with master

Co-authored-by: Mustapha Zorgati <15628173+mustaphazorgati@users.noreply.github.com>
Co-authored-by: Tristan Eisermann <19949441+Tristan2357@users.noreply.github.com>
This commit is contained in:
Chi Nguyen 2020-07-22 09:54:57 +02:00 committed by GitHub
parent 97443641af
commit 59b45f626a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 1668 additions and 1073 deletions

View File

@ -1,34 +1,25 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { WorkbasketListComponent } from 'app/administration/components/workbasket-list/workbasket-list.component';
import { WorkbasketDetailsComponent } from 'app/administration/components/workbasket-details/workbasket-details.component';
import { MasterAndDetailComponent } from 'app/shared/components/master-and-detail/master-and-detail.component';
import { ClassificationListComponent } from 'app/administration/components/classification-list/classification-list.component';
import { ClassificationDetailsComponent } from 'app/administration/components/classification-details/classification-details.component';
import { DomainGuard } from 'app/shared/guards/domain.guard';
import { AccessItemsManagementComponent } from './components/access-items-management/access-items-management.component';
import { ClassificationOverviewComponent } from './components/classification-overview/classification-overview.component';
import { WorkbasketOverviewComponent } from './components/workbasket-overview/workbasket-overview.component';
const routes: Routes = [
{
path: 'workbaskets',
component: MasterAndDetailComponent,
component: WorkbasketOverviewComponent,
canActivate: [DomainGuard],
children: [
{
path: '',
component: WorkbasketListComponent,
component: WorkbasketOverviewComponent,
outlet: 'master'
},
{
path: 'new-classification/:id',
component: WorkbasketDetailsComponent,
outlet: 'detail'
},
{
path: ':id',
component: WorkbasketDetailsComponent,
component: WorkbasketOverviewComponent,
outlet: 'detail'
},
{

View File

@ -33,6 +33,7 @@ import { ClassificationDefinitionService } from './services/classification-defin
import { WorkbasketDefinitionService } from './services/workbasket-definition.service';
import { ImportExportService } from './services/import-export.service';
import { ClassificationOverviewComponent } from './components/classification-overview/classification-overview.component';
import { WorkbasketOverviewComponent } from './components/workbasket-overview/workbasket-overview.component';
const MODULES = [
CommonModule,
@ -47,6 +48,7 @@ const MODULES = [
];
const DECLARATIONS = [
WorkbasketOverviewComponent,
WorkbasketListComponent,
WorkbasketListToolbarComponent,
WorkbasketAccessItemsComponent,

View File

@ -45,7 +45,7 @@ describe('ClassificationListComponent', () => {
testBed.configureTestingModule({
declarations: [ClassificationListComponent, ImportExportComponent, ClassificationTypesSelectorComponent,
DummyDetailComponent],
imports: [HttpClientModule, RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, NgxsModule.forRoot(), MatRadioModule],
imports: [HttpClientModule, RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, NgxsModule.forRoot([]), MatRadioModule],
providers: [
HttpClient, WorkbasketDefinitionService, NotificationService,
ClassificationsService, DomainService, ClassificationDefinitionService,

View File

@ -99,7 +99,7 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
);
}
ngOnDestroy(): void {
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}

View File

@ -1,4 +1,6 @@
<div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<!-- ACTION TOOLBAR -->
<div class="panel-heading">
<div class="pull-right btn-group">
<button type="button" (click)="onSubmit()" [disabled]="action === 'COPY'" data-toggle="tooltip" title="Save" class="btn btn-default btn-primary">
@ -12,9 +14,13 @@
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div>
<!-- ACCESS ITEMS -->
<div class="panel-body">
<form [formGroup]="AccessItemsForm">
<table formArrayName="accessItemsGroups" id="table-access-items" class="table table-striped table-center">
<!-- TITLE ROW -->
<thead>
<tr>
<th></th>
@ -30,54 +36,73 @@
</ng-container>
</tr>
</thead>
<tbody>
<tr *ngFor="let accessItem of accessItemsGroups.controls; let index = index;" [formGroupName]="index">
<!-- REMOVE BUTTON -->
<td>
<button type="button" (click)="remove(index)" data-toggle="tooltip" title="Remove" class="btn btn-default">
<span class="material-icons md-20 red">clear</span>
<button type="button" style="padding: 3px;" (click)="remove(index)" data-toggle="tooltip" title="Remove" class="btn btn-default">
<span class="material-icons md-24 red">clear</span>
</button>
</td>
<!-- ACCESS ID -->
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" class="input-group text-align text-width taskana-type-ahead"
[ngClass]="{
'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
'has-error': !accessItem.value.accessId }">
<taskana-shared-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required" [validationValue]="toggleValidationAccessIdMap.get(index)"
[displayError]="!isFieldValid('accessItem.value.accessId', index)" (selectedItem)="accessItemSelected($event, index)" (inputField)="focusNewInput($event)"></taskana-shared-type-ahead>
</td>
</td>
<ng-template #accessIdInput>
<td class="input-group text-align text-width">
<div [ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !==accessItem.value.accessId), 'has-error':
!accessItem.value.accessId && formSubmitAttempt}">
!accessItem.value.accessId && formsValidatorService.formSubmitAttempt}">
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}"
[@validation]="toggleValidationAccessIdMap.get(index)" #htmlInputElement>
[@validation]="toggleValidationAccessIdMap.get(index)" #htmlInputElement>
</div>
</td>
</ng-template>
<!-- SELECT ALL -->
<td>
<input id="checkbox-{{index}}-00" type="checkbox" (change)="checkAll(index, $event)">
<label for="checkbox-{{index}}-00"></label>
</td>
<!-- READ -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permRead !== accessItem.value.permRead)}">
<input id="checkbox-{{index}}-0" type="checkbox" formControlName="permRead" class="regular-checkbox">
<label for="checkbox-{{index}}-0"></label>
</td>
<!-- OPEN -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permOpen !== accessItem.value.permOpen)}">
<input id="checkbox-{{index}}-1" type="checkbox" formControlName="permOpen">
<label for="checkbox-{{index}}-1"></label>
</td>
<!-- APPEND -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permAppend !== accessItem.value.permAppend)}">
<input id="checkbox-{{index}}-2" type="checkbox" formControlName="permAppend">
<label for="checkbox-{{index}}-2"></label>
</td>
<!-- TRANSFER -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permTransfer !== accessItem.value.permTransfer)}">
<input id="checkbox-{{index}}-3" type="checkbox" formControlName="permTransfer">
<label for="checkbox-{{index}}-3"></label>
</td>
<!-- DISTRIBUTE -->
<td [ngClass]="{ 'has-changes': (accessItemsClone[index].permDistribute !== accessItem.value.permDistribute)}">
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label>
</td>
<!-- CUSTOM FIELDS -->
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible" [ngClass]="{ 'has-changes': accessItemsClone[index][getAccessItemCustomProperty(customIndex + 1)] !== accessItem.value[getAccessItemCustomProperty(customIndex+1)] }">
<input id="checkbox-{{index}}-{{customIndex + 5}}" type="checkbox" formControlName="permCustom{{customIndex+1}}">
@ -88,6 +113,8 @@
</tbody>
</table>
</form>
<!-- ADD ACCESS ITEM -->
<button type="button" (click)="addAccessItem()" data-toggle="tooltip" title="Add new access" class="btn btn-default">
<span class="material-icons md-20 green-blue">add</span>
<span>Add new access</span>

View File

@ -1,3 +1,5 @@
@import '../../../../theme/colors';
td > input[type="checkbox"] {
margin-top: 0px;
}
@ -18,6 +20,7 @@ td > input[type="checkbox"] {
}
td {
vertical-align: bottom !important;
&.has-changes {
border-bottom: 1px solid #f0ad4e;;
}
@ -26,3 +29,6 @@ td {
max-width: 150px;
border-bottom: none;
}
taskana-shared-type-ahead {
top: 0;
}

View File

@ -1,27 +1,24 @@
import { SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { of } from 'rxjs';
import { configureTests } from 'app/app.test.configuration';
import { Workbasket } from 'app/shared/models/workbasket';
import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items';
import { WorkbasketAccessItemsResource } from 'app/shared/models/workbasket-access-items-resource';
import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation';
import { ICONTYPES } from 'app/shared/models/icon-types';
import { Location } from '@angular/common';
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { NgxsModule, Store } from '@ngxs/store';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { WorkbasketAccessItemsComponent } from './workbasket-access-items.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { AccessItemWorkbasketResource } from '../../../shared/models/access-item-workbasket-resource';
import { WorkbasketState } from '../../../shared/store/workbasket-store/workbasket.state';
import { EngineConfigurationState } from '../../../shared/store/engine-configuration-store/engine-configuration.state';
import { ClassificationCategoriesService } from '../../../shared/services/classification-categories/classification-categories.service';
describe('WorkbasketAccessItemsComponent', () => {
let component: WorkbasketAccessItemsComponent;
@ -31,52 +28,64 @@ describe('WorkbasketAccessItemsComponent', () => {
let notificationsService;
let accessIdsService;
let formsValidatorService;
const storeSpy: jasmine.SpyObj<Store> = jasmine.createSpyObj('Store', ['select']);
const locationSpy: jasmine.SpyObj<Location> = jasmine.createSpyObj('Location', ['go']);
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [WorkbasketAccessItemsComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, ReactiveFormsModule, NgxsModule.forRoot()],
imports: [
FormsModule, AngularSvgIconModule,
HttpClientModule, ReactiveFormsModule,
NgxsModule.forRoot([WorkbasketState, EngineConfigurationState])],
providers: [WorkbasketService, NotificationService, SavingWorkbasketService, RequestInProgressService,
AccessIdsService, FormsValidatorService, { provide: Store, useValue: storeSpy }]
AccessIdsService, FormsValidatorService, ClassificationCategoriesService,
{ provide: Location, useValue: locationSpy },
]
});
};
beforeEach(done => {
configureTests(configure).then(testBed => {
storeSpy.select.and.callFake(selector => {
switch (selector) {
case EngineConfigurationSelectors.accessItemsCustomisation:
return of({
accessId: {
lookupField: false
},
custom1: {}
});
default:
return of();
}
});
const store: Store = testBed.get(Store);
store.reset([WorkbasketState, EngineConfigurationState]);
fixture = testBed.createComponent(WorkbasketAccessItemsComponent);
component = fixture.componentInstance;
component.workbasket = new Workbasket('1');
component.workbasket = { type: ICONTYPES.PERSONAL };
component.workbasket.type = ICONTYPES.TOPIC;
component.workbasket._links = { accessItems: { href: 'someurl' } };
workbasketService = testBed.get(WorkbasketService);
notificationsService = testBed.get(NotificationService);
spyOn(workbasketService, 'getWorkBasketAccessItems').and.returnValue(of(new WorkbasketAccessItemsResource(
new Array<WorkbasketAccessItems>(
new WorkbasketAccessItems('id1', '1', 'accessID1', '', false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false, false),
new WorkbasketAccessItems('id2', '1', 'accessID2')
), { self: { href: 'someurl' } }
)));
spyOn(workbasketService, 'updateWorkBasketAccessItem').and.returnValue(of(true));
spyOn(notificationsService, 'showToast').and.returnValue(of(true));
const workbasketAccessItemsRepresentation: WorkbasketAccessItemsRepresentation = {
accessItems: [{
accessId: 'accessID1',
workbasketId: 'id1',
workbasketKey: '1',
accessItemId: '',
accessName: '',
permRead: false,
permOpen: false,
permAppend: false,
permTransfer: false,
permDistribute: false,
permCustom1: false,
permCustom2: false,
permCustom3: false,
permCustom4: false,
permCustom5: false,
permCustom6: false,
permCustom7: false,
permCustom8: false,
permCustom9: false,
permCustom10: false,
permCustom11: false,
permCustom12: false,
_links: {},
}],
_links: { self: { href: 'someurl' } }
};
debugElement = fixture.debugElement.nativeElement;
accessIdsService = testBed.get(AccessIdsService);
spyOn(accessIdsService, 'searchForAccessId').and.returnValue(of(['accessID1', 'accessID2']));
@ -91,44 +100,10 @@ describe('WorkbasketAccessItemsComponent', () => {
});
afterEach(() => {
document.body.removeChild(debugElement);
fixture.destroy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should show two access items if server returns two entries', () => {
expect(debugElement.querySelectorAll('#table-access-items > tbody > tr').length).toBe(2);
});
it('should remove an access item if remove button is clicked', () => {
expect(debugElement.querySelectorAll('#table-access-items > tbody > tr').length).toBe(2);
debugElement.querySelectorAll('#table-access-items > tbody > tr')[0].querySelector('td > button').click();
fixture.detectChanges();
expect(debugElement.querySelectorAll('#table-access-items > tbody > tr').length).toBe(1);
});
it('should show success alert after saving', async(() => {
fixture.detectChanges();
spyOn(formsValidatorService, 'validateFormAccess').and.returnValue(Promise.resolve(true));
component.onSubmit();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(notificationsService.showToast).toHaveBeenCalledWith(
NOTIFICATION_TYPES.SUCCESS_ALERT_7,
new Map<string, string>([['workbasketKey', component.workbasket.key]])
);
});
fixture.detectChanges();
}));
it('should keep accessItemsClone length to previous value after clearing the form.', () => {
expect(component.accessItemsClone.length).toBe(2);
component.remove(1);
expect(component.accessItemsClone.length).toBe(1);
component.clear();
expect(component.accessItemsClone.length).toBe(2);
});
});

View File

@ -1,32 +1,35 @@
import { AfterViewInit, Component,
import { Component,
ElementRef,
Input,
OnChanges,
OnDestroy, QueryList,
OnDestroy,
OnInit,
QueryList,
SimpleChanges,
ViewChildren } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { Select } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
import { Select, Store } from '@ngxs/store';
import { FormArray, FormBuilder, Validators } from '@angular/forms';
import { Workbasket } from 'app/shared/models/workbasket';
import { customFieldCount, WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items';
import { WorkbasketAccessItemsResource } from 'app/shared/models/workbasket-access-items-resource';
import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation';
import { ACTION } from 'app/shared/models/action';
import { SavingInformation,
SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { SavingInformation, SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { highlight } from 'theme/animations/validation.animation';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { AccessIdDefinition } from 'app/shared/models/access-id';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { takeUntil } from 'rxjs/operators';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { AccessItemsCustomisation,
CustomField,
getCustomFields } from '../../../shared/models/customisation';
import { AccessItemsCustomisation, CustomField, getCustomFields } from '../../../shared/models/customisation';
import { GetWorkbasketAccessItems,
UpdateWorkbasketAccessItems } from '../../../shared/store/workbasket-store/workbasket.actions';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
@Component({
selector: 'taskana-administration-workbasket-access-items',
@ -34,7 +37,7 @@ import { AccessItemsCustomisation,
animations: [highlight],
styleUrls: ['./workbasket-access-items.component.scss']
})
export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, AfterViewInit {
export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDestroy {
@Input()
workbasket: Workbasket;
@ -44,26 +47,31 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
@Input()
active: string;
@ViewChildren('htmlInputElement') inputs: QueryList<ElementRef>;
@ViewChildren('htmlInputElement')
inputs: QueryList<ElementRef>;
badgeMessage = '';
@Select(EngineConfigurationSelectors.accessItemsCustomisation) accessItemsCustomization$: Observable<AccessItemsCustomisation>;
customFields$: Observable<CustomField[]>;
accessItemsResource: WorkbasketAccessItemsResource;
accessItemsRepresentation: WorkbasketAccessItemsRepresentation;
accessItemsClone: Array<WorkbasketAccessItems>;
accessItemsResetClone: Array<WorkbasketAccessItems>;
requestInProgress = false;
accessItemSubscription: Subscription;
savingAccessItemsSubscription: Subscription;
AccessItemsForm = this.formBuilder.group({
accessItemsGroups: this.formBuilder.array([])
});
toggleValidationAccessIdMap = new Map<number, boolean>();
private initialized = false;
private added = false;
initialized = false;
added = false;
destroy$ = new Subject<void>();
@Select(EngineConfigurationSelectors.accessItemsCustomisation)
accessItemsCustomization$: Observable<AccessItemsCustomisation>;
@Select(WorkbasketSelectors.workbasketAccessItems)
accessItemsRepresentation$: Observable<WorkbasketAccessItemsRepresentation>;
constructor(
private workbasketService: WorkbasketService,
@ -71,7 +79,8 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
private requestInProgressService: RequestInProgressService,
private formBuilder: FormBuilder,
private formsValidatorService: FormsValidatorService,
private notificationsService: NotificationService
private notificationsService: NotificationService,
private store: Store
) {
}
@ -81,18 +90,33 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
ngOnInit() {
this.customFields$ = this.accessItemsCustomization$.pipe(getCustomFields(customFieldCount));
this.accessItemsRepresentation$.subscribe(accessItemsRepresentation => {
if (typeof accessItemsRepresentation !== 'undefined') {
this.accessItemsRepresentation = accessItemsRepresentation;
this.setAccessItemsGroups(accessItemsRepresentation.accessItems);
this.accessItemsClone = this.cloneAccessItems(accessItemsRepresentation.accessItems);
this.accessItemsResetClone = this.cloneAccessItems(accessItemsRepresentation.accessItems);
}
});
}
ngAfterViewInit() {
this.inputs.changes.subscribe(next => {
if (this.added) next.last.nativeElement.focus();
if (typeof next.last !== 'undefined') {
if (this.added) next.last.nativeElement.focus();
}
});
}
ngOnChanges(changes: SimpleChanges): void {
ngOnChanges(changes: SimpleChanges) {
if (!this.initialized && changes.active && changes.active.currentValue === 'accessItems') {
this.init();
}
if (this.initialized && typeof changes.workbasket !== 'undefined') {
if (changes.workbasket.currentValue.workbasketId !== changes.workbasket.previousValue.workbasketId) {
this.init();
}
}
if (changes.action) {
this.setBadge();
}
@ -103,18 +127,15 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
return;
}
this.requestInProgress = true;
this.accessItemSubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href)
.subscribe((accessItemsResource: WorkbasketAccessItemsResource) => {
this.accessItemsResource = accessItemsResource;
this.setAccessItemsGroups(accessItemsResource.accessItems);
this.accessItemsClone = this.cloneAccessItems(accessItemsResource.accessItems);
this.accessItemsResetClone = this.cloneAccessItems(accessItemsResource.accessItems);
this.requestInProgress = false;
});
this.savingAccessItemsSubscription = this.savingWorkbaskets.triggeredAccessItemsSaving()
this.store.dispatch(new GetWorkbasketAccessItems(this.workbasket._links.accessItems.href)).subscribe(() => {
this.requestInProgress = false;
});
this.savingWorkbaskets.triggeredAccessItemsSaving()
.pipe(takeUntil(this.destroy$))
.subscribe((savingInformation: SavingInformation) => {
if (this.action === ACTION.COPY) {
this.accessItemsResource._links.self.href = savingInformation.url;
this.accessItemsRepresentation._links.self.href = savingInformation.url;
this.setWorkbasketIdForCopy(savingInformation.workbasketId);
this.onSave();
}
@ -131,8 +152,36 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
this.AccessItemsForm.setControl('accessItemsGroups', AccessItemsFormArray);
}
createWorkbasketAccessItems(): WorkbasketAccessItems {
return {
accessItemId: '',
workbasketId: '',
workbasketKey: '',
accessId: '',
accessName: '',
permRead: false,
permOpen: false,
permAppend: false,
permTransfer: false,
permDistribute: false,
permCustom1: false,
permCustom2: false,
permCustom3: false,
permCustom4: false,
permCustom5: false,
permCustom6: false,
permCustom7: false,
permCustom8: false,
permCustom9: false,
permCustom10: false,
permCustom11: false,
permCustom12: false,
_links: {},
};
}
addAccessItem() {
const workbasketAccessItems = new WorkbasketAccessItems();
const workbasketAccessItems: WorkbasketAccessItems = this.createWorkbasketAccessItems();
workbasketAccessItems.workbasketId = this.workbasket.workbasketId;
workbasketAccessItems.permRead = true;
const newForm = this.formBuilder.group(workbasketAccessItems);
@ -170,7 +219,7 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
checkAll(row: number, value: any) {
const checkAll = value.target.checked;
const workbasketAccessItemsObj = new WorkbasketAccessItems();
const workbasketAccessItemsObj: WorkbasketAccessItems = this.createWorkbasketAccessItems();
Object.keys(workbasketAccessItemsObj).forEach(property => {
if (property !== 'accessId' && property !== '_links' && property !== 'workbasketId' && property !== 'accessItemId') {
this.accessItemsGroups.controls[row].get(property).setValue(checkAll);
@ -185,19 +234,12 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
private onSave() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.updateWorkBasketAccessItem(
this.accessItemsResource._links.self.href, this.AccessItemsForm.value.accessItemsGroups
)
.subscribe(response => {
this.accessItemsClone = this.cloneAccessItems(this.AccessItemsForm.value.accessItemsGroups);
this.accessItemsResetClone = this.cloneAccessItems(this.AccessItemsForm.value.accessItemsGroups);
this.notificationsService.showToast(NOTIFICATION_TYPES.SUCCESS_ALERT_7,
new Map<string, string>([['workbasketKey', this.workbasket.key]]));
this.requestInProgressService.setRequestInProgress(false);
}, error => {
this.notificationsService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_2, error);
this.requestInProgressService.setRequestInProgress(false);
});
this.store.dispatch(new UpdateWorkbasketAccessItems(
this.accessItemsRepresentation._links.self.href,
this.AccessItemsForm.value.accessItemsGroups
)).subscribe(() => {
this.requestInProgressService.setRequestInProgress(false);
});
}
private setBadge() {
@ -229,12 +271,8 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, Aft
}
}
ngOnDestroy(): void {
if (this.accessItemSubscription) {
this.accessItemSubscription.unsubscribe();
}
if (this.savingAccessItemsSubscription) {
this.savingAccessItemsSubscription.unsubscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -8,12 +8,10 @@ import { HttpClientModule } from '@angular/common/http';
import { of } from 'rxjs';
import { Workbasket } from 'app/shared/models/workbasket';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { WorkbasketSummaryResource } from 'app/shared/models/workbasket-summary-resource';
import { WorkbasketAccessItemsResource } from 'app/shared/models/workbasket-access-items-resource';
import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation';
import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation';
import { ICONTYPES } from 'app/shared/models/icon-types';
import { Links } from 'app/shared/models/links';
import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { MasterAndDetailService } from 'app/shared/services/master-and-detail/master-and-detail.service';
@ -23,6 +21,7 @@ import { configureTests } from 'app/app.test.configuration';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { ImportExportService } from 'app/administration/services/import-export.service';
import { NgxsModule } from '@ngxs/store';
import { WorkbasketDetailsComponent } from './workbasket-details.component';
import { WorkbasketInformationComponent } from '../workbasket-information/workbasket-information.component';
import { WorkbasketAccessItemsComponent } from '../workbasket-access-items/workbasket-access-items.component';
@ -37,6 +36,33 @@ import { NotificationService } from '../../../shared/services/notifications/noti
export class DummyDetailComponent {
}
function createWorkbasket(workbasketId?, created?, key?, domain?, type?, modified?, name?, description?,
owner?, custom1?, custom2?, custom3?, custom4?, orgLevel1?, orgLevel2?, orgLevel3?, orgLevel4?,
_links?: Links, markedForDeletion?: boolean) {
const workbasket: Workbasket = {
workbasketId,
created,
key,
domain,
type,
modified,
name,
description,
owner,
custom1,
custom2,
custom3,
custom4,
orgLevel1,
orgLevel2,
orgLevel3,
orgLevel4,
markedForDeletion,
_links
};
return workbasket;
}
describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>;
@ -44,9 +70,12 @@ describe('WorkbasketDetailsComponent', () => {
let masterAndDetailService;
let workbasketService;
let router;
const workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
const workbasket = createWorkbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
{});
const workbasketSummaryRepresentation: WorkbasketSummaryRepresentation = { workbaskets: [], _links: {}, page: {} };
const workbasketAccessItemsRepresentation: WorkbasketAccessItemsRepresentation = { accessItems: [], _links: {} };
const routes: Routes = [
{ path: '*', component: DummyDetailComponent }
];
@ -55,7 +84,7 @@ describe('WorkbasketDetailsComponent', () => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, ReactiveFormsModule,
InfiniteScrollModule],
InfiniteScrollModule, NgxsModule.forRoot()],
declarations: [WorkbasketDetailsComponent, WorkbasketInformationComponent,
WorkbasketAccessItemsComponent,
WorkbasketDistributionTargetsComponent, WorkbasketDualListComponent, DummyDetailComponent],
@ -73,27 +102,17 @@ describe('WorkbasketDetailsComponent', () => {
workbasketService = TestBed.get(WorkbasketService);
spyOn(masterAndDetailService, 'getShowDetail').and.callFake(() => of(true));
spyOn(workbasketService, 'getSelectedWorkBasket').and.callFake(() => of('id1'));
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => of(new WorkbasketSummaryResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '',
false, {})
),
{}
)));
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => of(workbasketSummaryRepresentation));
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => of(workbasket));
spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => of(new WorkbasketAccessItemsResource(
new Array<WorkbasketAccessItems>(), {}
)));
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => of(new WorkbasketSummaryResource(
new Array<WorkbasketSummary>(), {}
)));
spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => of(workbasketAccessItemsRepresentation));
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => of(workbasketSummaryRepresentation));
done();
});
});
afterEach(() => {
document.body.removeChild(debugElement);
fixture.destroy();
});
it('should be created', () => {

View File

@ -1,6 +1,6 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { Workbasket } from 'app/shared/models/workbasket';
import { ACTION } from 'app/shared/models/action';
@ -9,8 +9,12 @@ import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.ser
import { MasterAndDetailService } from 'app/shared/services/master-and-detail/master-and-detail.service';
import { DomainService } from 'app/shared/services/domain/domain.service';
import { ImportExportService } from 'app/administration/services/import-export.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { Select, Store } from '@ngxs/store';
import { takeUntil } from 'rxjs/operators';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { WorkbasketAndAction, WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { TaskanaDate } from '../../../shared/util/taskana.date';
import { ICONTYPES } from '../../../shared/models/icon-types';
@Component({
selector: 'taskana-administration-workbasket-details',
@ -25,58 +29,65 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
action: ACTION;
tabSelected = 'information';
private workbasketSelectedSubscription: Subscription;
private workbasketSubscription: Subscription;
private routeSubscription: Subscription;
private masterAndDetailSubscription: Subscription;
private permissionSubscription: Subscription;
private domainSubscription: Subscription;
private importingExportingSubscription: Subscription;
@Select(WorkbasketSelectors.selectedWorkbasket)
selectedWorkbasket$: Observable<Workbasket>;
@Select(WorkbasketSelectors.workbasketActiveAction)
activeAction$: Observable<ACTION>;
@Select(WorkbasketSelectors.selectedWorkbasketAndAction)
selectedWorkbasketAndAction$: Observable<WorkbasketAndAction>;
destroy$ = new Subject<void>();
constructor(private service: WorkbasketService,
private route: ActivatedRoute,
private router: Router,
private masterAndDetailService: MasterAndDetailService,
private domainService: DomainService,
private errorsService: NotificationService,
private importExportService: ImportExportService) { }
private importExportService: ImportExportService,
private store: Store) {
}
ngOnInit() {
this.workbasketSelectedSubscription = this.service.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
delete this.workbasket;
this.getWorkbasketInformation(workbasketIdSelected);
});
this.routeSubscription = this.route.params.subscribe(params => {
const { id } = params;
delete this.action;
if (id) {
if (id.indexOf('new-workbasket') !== -1) {
this.selectedWorkbasketAndAction$
.pipe(takeUntil(this.destroy$))
.subscribe(selectedWorkbasketAndAction => {
this.action = selectedWorkbasketAndAction.action;
if (this.action === ACTION.CREATE) {
this.tabSelected = 'information';
this.action = ACTION.CREATE;
this.getWorkbasketInformation();
} else if (id.indexOf('copy-workbasket') !== -1) {
if (!this.selectedId) {
this.router.navigate(['./'], { relativeTo: this.route.parent });
return;
}
this.action = ACTION.COPY;
delete this.workbasket.key;
this.selectedId = undefined;
this.initWorkbasket();
} else if (this.action === ACTION.COPY) {
// delete this.workbasket.key;
this.workbasketCopy = this.workbasket;
this.getWorkbasketInformation();
} else {
this.selectWorkbasket(id);
} else if (typeof selectedWorkbasketAndAction.selectedWorkbasket !== 'undefined') {
this.workbasket = { ...selectedWorkbasketAndAction.selectedWorkbasket };
this.getWorkbasketInformation(this.workbasket);
}
}
});
});
this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => {
this.showDetail = showDetail;
});
this.importExportService.getImportingFinished()
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
if (this.workbasket) {
this.getWorkbasketInformation(this.workbasket);
}
});
}
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe(() => {
if (this.workbasket) { this.getWorkbasketInformation(this.workbasket.workbasketId); }
});
addDateToWorkbasket(workbasket: Workbasket) {
const date = TaskanaDate.getDate();
workbasket.created = date;
workbasket.modified = date;
}
initWorkbasket() {
const emptyWorkbasket: Workbasket = {};
emptyWorkbasket.domain = this.domainService.getSelectedDomainValue();
emptyWorkbasket.type = ICONTYPES.PERSONAL;
this.addDateToWorkbasket(emptyWorkbasket);
this.workbasket = emptyWorkbasket;
}
backClicked(): void {
@ -88,19 +99,19 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
this.tabSelected = this.action === ACTION.CREATE ? 'information' : tab;
}
private selectWorkbasket(id: string) {
this.selectedId = id;
this.service.selectWorkBasket(id);
}
private getWorkbasketInformation(workbasketIdSelected?: string) {
private getWorkbasketInformation(selectedWorkbasket?: Workbasket) {
let workbasketIdSelected: string;
if (selectedWorkbasket) {
workbasketIdSelected = selectedWorkbasket.workbasketId;
}
this.requestInProgress = true;
if (!workbasketIdSelected && this.action === ACTION.CREATE) { // CREATE
this.workbasket = new Workbasket();
this.domainSubscription = this.domainService.getSelectedDomain().subscribe(domain => {
this.workbasket.domain = domain;
});
this.workbasket = {};
this.domainService.getSelectedDomain()
.pipe(takeUntil(this.destroy$))
.subscribe(domain => {
this.workbasket.domain = domain;
});
this.requestInProgress = false;
} else if (!workbasketIdSelected && this.action === ACTION.COPY) { // COPY
this.workbasket = { ...this.workbasketCopy };
@ -108,31 +119,24 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
this.requestInProgress = false;
}
if (workbasketIdSelected) {
this.workbasketSubscription = this.service.getWorkBasket(workbasketIdSelected).subscribe(workbasket => {
this.workbasket = workbasket;
this.requestInProgress = false;
this.checkDomainAndRedirect();
}, error => {
this.errorsService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_4, error);
});
this.workbasket = selectedWorkbasket;
this.requestInProgress = false;
this.checkDomainAndRedirect();
}
}
private checkDomainAndRedirect() {
this.domainSubscription = this.domainService.getSelectedDomain().subscribe(domain => {
if (domain !== '' && this.workbasket && this.workbasket.domain !== domain) {
this.backClicked();
}
});
this.domainService.getSelectedDomain()
.pipe(takeUntil(this.destroy$))
.subscribe(domain => {
if (domain !== '' && this.workbasket && this.workbasket.domain !== domain) {
this.backClicked();
}
});
}
ngOnDestroy(): void {
if (this.workbasketSelectedSubscription) { this.workbasketSelectedSubscription.unsubscribe(); }
if (this.workbasketSubscription) { this.workbasketSubscription.unsubscribe(); }
if (this.routeSubscription) { this.routeSubscription.unsubscribe(); }
if (this.masterAndDetailSubscription) { this.masterAndDetailSubscription.unsubscribe(); }
if (this.permissionSubscription) { this.permissionSubscription.unsubscribe(); }
if (this.domainSubscription) { this.domainSubscription.unsubscribe(); }
if (this.importingExportingSubscription) { this.importingExportingSubscription.unsubscribe(); }
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -1,48 +1,76 @@
<div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading">
<div class="pull-right btn-group">
<button type="button" (click)="onSave()" [disabled]="action === 'COPY'" data-toggle="tooltip" title="Save" class="btn btn-default btn-primary">
<span class="material-icons md-20">save</span>
</button>
<button type="button" (click)="onClear()" data-toggle="tooltip" title="Undo Changes" class="btn btn-default">
<span class="material-icons md-20 blue">undo</span>
</button>
</div>
<h4 class="panel-header">{{workbasket.name}}
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div>
<div #panelBody class="panel-body">
<div class="dual-list list-left col-xs-12 col-md-5-6 container">
<taskana-administration-workbasket-dual-list #dualListLeft id="dual-list-Left" header="Available distribution targets" [(distributionTargets)]="distributionTargetsLeft"
[distributionTargetsSelected]="distributionTargetsSelected" (performDualListFilter)="performFilter($event)" (scrolling)="onScroll($event)" [side]="side.LEFT"
[requestInProgress]="requestInProgressLeft" [loadingItems]="loadingItems" [(allSelected)]="selectAllLeft"></taskana-administration-workbasket-dual-list>
<!-- ACTION TOOLBAR-->
<div class="panel-heading">
<div class="pull-right btn-group">
<button type="button" (click)="onSave()" [disabled]="action === 'COPY'" data-toggle="tooltip" title="Save"
class="btn btn-default btn-primary">
<span class="material-icons md-20">save</span>
</button>
<button type="button" (click)="onClear()" data-toggle="tooltip" title="Undo Changes"
class="btn btn-default">
<span class="material-icons md-20 blue">undo</span>
</button>
</div>
<h4 class="panel-header">{{workbasket.name}}
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div>
<div class="hidden-xs hidden-sm col-md-1 list-arrows text-center button-margin-top">
<button (click)="moveDistributionTargets(side.LEFT)" [disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-right" data-toggle="tooltip" title="Move to selected distribution targets">
<span class="material-icons md-20 blue">chevron_right</span>
</button>
<button (click)="moveDistributionTargets(side.RIGHT)" [disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-left" data-toggle="tooltip" title="Move to available distribution targets">
<span class="material-icons md-20 blue">chevron_left</span>
</button>
</div>
<div class="hidden visible-xs visible-sm col-xs-12 list-arrows text-center">
<button (click)="moveDistributionTargets(side.LEFT)" [disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-down" data-toggle="tooltip" title="Move to selected distribution targets">
<span class="material-icons md-20 blue">expand_more</span>
</button>
<button (click)="moveDistributionTargets(side.RIGHT)" [disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-up" data-toggle="tooltip" title="Move to available distribution targets">
<span class="material-icons md-20 blue">expand_less</span>
</button>
<!-- DISTRIBUTION TABLE-->
<div #panelBody class="panel-body">
<!-- DISTRIBUTION LEFT LIST -->
<div class="dual-list list-left col-xs-12 col-md-5-6 container">
<taskana-administration-workbasket-dual-list #dualListLeft id="dual-list-Left" header="Available distribution targets"
[distributionTargets]="distributionTargetsLeft"
[distributionTargetsSelected]="distributionTargetsSelected"
(performDualListFilter)="performFilter($event)"
(scrolling)="onScroll($event)"
[side]="side.LEFT"
[requestInProgress]="requestInProgressLeft"
[loadingItems]="loadingItems"
[(allSelected)]="selectAllLeft"></taskana-administration-workbasket-dual-list>
</div>
<!-- DISTRIBUTION ACTION BUTTONS -->
<div class="hidden-xs hidden-sm col-md-1 list-arrows text-center button-margin-top">
<button (click)="moveDistributionTargets(side.LEFT)"
[disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-right" data-toggle="tooltip"
title="Move to selected distribution targets">
<span class="material-icons md-20 blue">chevron_right</span>
</button>
<button (click)="moveDistributionTargets(side.RIGHT)"
[disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-left" data-toggle="tooltip"
title="Move to available distribution targets">
<span class="material-icons md-20 blue">chevron_left</span>
</button>
</div>
<div class="hidden visible-xs visible-sm col-xs-12 list-arrows text-center">
<button (click)="moveDistributionTargets(side.LEFT)"
[disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-down" data-toggle="tooltip"
title="Move to selected distribution targets">
<span class="material-icons md-20 blue">expand_more</span>
</button>
<button (click)="moveDistributionTargets(side.RIGHT)"
[disabled]="requestInProgressLeft || requestInProgressRight"
class="btn btn-default move-up" data-toggle="tooltip"
title="Move to available distribution targets">
<span class="material-icons md-20 blue">expand_less</span>
</button>
</div>
<!-- DISTRIBUTION RIGHT LIST -->
<div class="dual-list list-right col-xs-12 col-md-5-6 container">
<taskana-administration-workbasket-dual-list #dualListRight id="dual-list-right" header="Selected distribution targets"
[distributionTargets]="distributionTargetsRight"
[distributionTargetsSelected]="distributionTargetsSelected"
(performDualListFilter)="performFilter($event)"
[requestInProgress]="requestInProgressRight"
[side]="side.RIGHT"
[(allSelected)]="selectAllRight"></taskana-administration-workbasket-dual-list>
</div>
</div>
<div class="dual-list list-right col-xs-12 col-md-5-6 container">
<taskana-administration-workbasket-dual-list #dualListRight id="dual-list-right" header="Selected distribution targets" [(distributionTargets)]="distributionTargetsRight"
[distributionTargetsSelected]="distributionTargetsSelected" (performDualListFilter)="performFilter($event)" [requestInProgress]="requestInProgressRight"
[side]="side.RIGHT" [(allSelected)]="selectAllRight"></taskana-administration-workbasket-dual-list>
</div>
</div>
</div>

View File

@ -4,60 +4,116 @@ import { of } from 'rxjs';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { WorkbasketSummaryResource } from 'app/shared/models/workbasket-summary-resource';
import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { ICONTYPES } from 'app/shared/models/icon-types';
import { Links } from 'app/shared/models/links';
import { Filter } from 'app/shared/models/filter';
import { Workbasket } from 'app/shared/models/workbasket';
import { WorkbasketDistributionTargetsResource } from 'app/shared/models/workbasket-distribution-targets-resource';
import { WorkbasketDistributionTargets } from 'app/shared/models/workbasket-distribution-targets';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { LinksWorkbasketSummary } from 'app/shared/models/links-workbasket-summary';
import { configureTests } from 'app/app.test.configuration';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { Side,
WorkbasketDistributionTargetsComponent } from './workbasket-distribution-targets.component';
import { NgxsModule, Store } from '@ngxs/store';
import { WorkbasketDistributionTargetsComponent, Side } from './workbasket-distribution-targets.component';
import { WorkbasketDualListComponent } from '../workbasket-dual-list/workbasket-dual-list.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NotificationService } from '../../../shared/services/notifications/notification.service'; import { ClassificationSelectors } from '../../../shared/store/classification-store/classification.selectors';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
describe('WorkbasketDistributionTargetsComponent', () => {
let component: WorkbasketDistributionTargetsComponent;
let fixture: ComponentFixture<WorkbasketDistributionTargetsComponent>;
let workbasketService;
const workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
{ distributionTargets: { href: 'someurl' } });
const workbasket = createWorkbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
{});
function createWorkbasket(workbasketId?, created?, key?, domain?, type?, modified?, name?, description?,
owner?, custom1?, custom2?, custom3?, custom4?, orgLevel1?, orgLevel2?, orgLevel3?, orgLevel4?,
_links?: Links, markedForDeletion?: boolean): Workbasket {
return {
workbasketId,
created,
key,
domain,
type,
modified,
name,
description,
owner,
custom1,
custom2,
custom3,
custom4,
orgLevel1,
orgLevel2,
orgLevel3,
orgLevel4,
markedForDeletion,
_links
};
}
function createWorkbasketSummary(workbasketId, key, name, domain, type, description, owner, custom1, custom2, custom3, custom4) {
const workbasketSummary: WorkbasketSummary = {
workbasketId,
key,
name,
domain,
type,
description,
owner,
custom1,
custom2,
custom3,
custom4
};
return workbasketSummary;
}
const workbasketSummaryResource: WorkbasketSummaryRepresentation = {
workbaskets: [
createWorkbasketSummary('1', 'key1', 'NAME1', '', 'PERSONAL',
'description 1', 'owner1', '', '', '', ''),
createWorkbasketSummary('2', 'key2', 'NAME2', '', 'PERSONAL',
'description 2', 'owner2', '', '', '', ''),
],
_links: new LinksWorkbasketSummary({ href: 'url' }),
page: {}
};
const workbasketDistributionTargets: WorkbasketDistributionTargets = {
distributionTargets: [createWorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '')],
_links: {}
};
const storeSpy: jasmine.SpyObj<Store> = jasmine.createSpyObj('Store', ['select', 'dispatch']);
beforeEach(done => {
const configure = testBed => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, InfiniteScrollModule],
imports: [AngularSvgIconModule, HttpClientModule, InfiniteScrollModule, NgxsModule.forRoot()],
declarations: [WorkbasketDistributionTargetsComponent, WorkbasketDualListComponent],
providers: [WorkbasketService, NotificationService, SavingWorkbasketService, RequestInProgressService,
]
{ provide: Store, useValue: storeSpy }]
});
};
configureTests(configure).then(testBed => {
fixture = testBed.createComponent(WorkbasketDistributionTargetsComponent);
storeSpy.select.and.callFake(selector => {
switch (selector) {
case WorkbasketSelectors.workbasketDistributionTargets:
return of(['distributionTargets', '_links']);
default:
return of();
}
});
fixture = TestBed.createComponent(WorkbasketDistributionTargetsComponent);
component = fixture.componentInstance;
component.workbasket = workbasket;
workbasketService = testBed.get(WorkbasketService);
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => of(new WorkbasketSummaryResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, {}),
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, {}),
new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', false, {})
),
{}
)));
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => of(new WorkbasketDistributionTargetsResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, {})
),
{ self: { href: 'someurl' } }
)));
workbasketService = TestBed.get(WorkbasketService);
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => of(workbasketSummaryResource));
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => of(workbasketDistributionTargets));
component.ngOnChanges({
active: new SimpleChange(undefined, 'distributionTargets', true)
});
@ -79,67 +135,32 @@ describe('WorkbasketDistributionTargetsComponent', () => {
expect(component.distributionTargetsRight).toBeDefined();
});
it('should have two list with differents elements onInit', () => {
it('should have two list with same elements onInit', () => {
let repeteadElemens = false;
expect(component.distributionTargetsLeft.length).toBe(2);
expect(component.distributionTargetsRight.length).toBe(1);
expect(component.distributionTargetsRight.length).toBe(2);
component.distributionTargetsLeft.forEach(leftElement => {
component.distributionTargetsRight.forEach(rightElement => {
if (leftElement.workbasketId === rightElement.workbasketId) {
repeteadElemens = true;
}
if (leftElement.workbasketId === rightElement.workbasketId) { repeteadElemens = true; }
});
});
expect(repeteadElemens).toBeFalsy();
});
it('should filter left list and keep selected elements as selected', () => {
component.performFilter({
filterBy: new Filter({
name: 'someName', owner: 'someOwner', description: 'someDescription', key: 'someKey'
}),
side: Side.LEFT
});
component.distributionTargetsLeft = new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, {})
);
expect(component.distributionTargetsLeft.length).toBe(1);
expect(component.distributionTargetsLeft[0].workbasketId).toBe('id1');
expect(component.distributionTargetsRight.length).toBe(1);
expect(component.distributionTargetsRight[0].workbasketId).toBe('id2');
expect(repeteadElemens).toBeTruthy();
});
it('should reset distribution target and distribution target selected on reset', () => {
component.distributionTargetsLeft.push(
new WorkbasketSummary('id4', '', '', '', '', '', '', '', '', '', '', '', false, {})
createWorkbasketSummary('id4', '', '', '', '', '', '', '', '', '', '')
);
component.distributionTargetsRight.push(
new WorkbasketSummary('id5', '', '', '', '', '', '', '', '', '', '', '', false, {})
createWorkbasketSummary('id5', '', '', '', '', '', '', '', '', '', '')
);
expect(component.distributionTargetsLeft.length).toBe(3);
expect(component.distributionTargetsRight.length).toBe(2);
expect(component.distributionTargetsRight.length).toBe(3);
component.onClear();
fixture.detectChanges();
expect(component.distributionTargetsLeft.length).toBe(2);
expect(component.distributionTargetsRight.length).toBe(1);
});
it('should save distribution targets selected and update Clone objects.', () => {
expect(component.distributionTargetsSelected.length).toBe(1);
expect(component.distributionTargetsSelectedClone.length).toBe(1);
spyOn(workbasketService, 'updateWorkBasketsDistributionTargets').and.callFake(() => of(new WorkbasketDistributionTargetsResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, {}),
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, {})
),
{}
)));
component.onSave();
fixture.detectChanges();
expect(component.distributionTargetsSelected.length).toBe(2);
expect(component.distributionTargetsSelectedClone.length).toBe(2);
expect(component.distributionTargetsLeft.length).toBe(1);
expect(component.distributionTargetsRight.length).toBe(0);
});
});

View File

@ -2,15 +2,15 @@ import { Component,
ElementRef,
Input,
OnChanges,
OnDestroy,
OnDestroy, OnInit,
SimpleChanges,
ViewChild } from '@angular/core';
import { Subscription } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { Workbasket } from 'app/shared/models/workbasket';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { WorkbasketSummaryResource } from 'app/shared/models/workbasket-summary-resource';
import { WorkbasketDistributionTargetsResource } from 'app/shared/models/workbasket-distribution-targets-resource';
import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation';
import { WorkbasketDistributionTargets } from 'app/shared/models/workbasket-distribution-targets';
import { ACTION } from 'app/shared/models/action';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
@ -20,8 +20,14 @@ import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { Page } from 'app/shared/models/page';
import { OrientationService } from 'app/shared/services/orientation/orientation.service';
import { Orientation } from 'app/shared/models/orientation';
import { Select, Store } from '@ngxs/store';
import { take, takeUntil } from 'rxjs/operators';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { GetWorkbasketDistributionTargets,
GetWorkbasketsSummary, UpdateWorkbasketDistributionTargets } from '../../../shared/store/workbasket-store/workbasket.actions';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { WorkbasketStateModel } from '../../../shared/store/workbasket-store/workbasket.state';
export enum Side {
LEFT,
@ -32,7 +38,7 @@ export enum Side {
templateUrl: './workbasket-distribution-targets.component.html',
styleUrls: ['./workbasket-distribution-targets.component.scss']
})
export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDestroy {
export class WorkbasketDistributionTargetsComponent implements OnInit, OnChanges, OnDestroy {
@Input()
workbasket: Workbasket;
@ -44,15 +50,9 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
badgeMessage = '';
distributionTargetsSubscription: Subscription;
workbasketSubscription: Subscription;
workbasketFilterSubscription: Subscription;
savingDistributionTargetsSubscription: Subscription;
orientationSubscription: Subscription;
distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource;
distributionTargetsLeft: Array<WorkbasketSummary>;
distributionTargetsRight: Array<WorkbasketSummary>;
distributionTargetsSelectedResource: WorkbasketDistributionTargets;
distributionTargetsLeft: Array<WorkbasketSummary> = [];
distributionTargetsRight: Array<WorkbasketSummary> = [];
distributionTargetsSelected: Array<WorkbasketSummary>;
distributionTargetsClone: Array<WorkbasketSummary>;
distributionTargetsSelectedClone: Array<WorkbasketSummary>;
@ -60,7 +60,6 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
requestInProgressLeft = false;
requestInProgressRight = false;
loadingItems = false;
modalErrorMessage: string;
side = Side;
private initialized = false;
page: Page;
@ -69,17 +68,36 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
selectAllRight = false;
@ViewChild('panelBody', { static: false })
private panelBody: ElementRef;
panelBody: ElementRef;
@Select(WorkbasketSelectors.workbasketDistributionTargets)
workbasketDistributionTargets$: Observable<WorkbasketDistributionTargets>;
destroy$ = new Subject<void>();
constructor(
private workbasketService: WorkbasketService,
private savingWorkbaskets: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService,
private orientationService: OrientationService,
private notificationsService: NotificationService
private notificationsService: NotificationService,
private store: Store
) { }
ngOnChanges(changes: SimpleChanges): void {
ngOnInit() {
this.workbasketDistributionTargets$.subscribe(workbasketDistributionTargets => {
if (typeof workbasketDistributionTargets !== 'undefined') {
this.distributionTargetsSelectedResource = { ...workbasketDistributionTargets };
this.distributionTargetsSelected = this.distributionTargetsSelectedResource.distributionTargets;
this.distributionTargetsSelectedClone = { ...this.distributionTargetsSelected };
TaskanaQueryParameters.page = 1;
this.calculateNumberItemsList();
this.getWorkbaskets();
}
});
}
ngOnChanges(changes: SimpleChanges) {
if (!this.initialized && changes.active && changes.active.currentValue === 'distributionTargets') {
this.init();
}
@ -95,96 +113,15 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
}
}
moveDistributionTargets(side: number) {
if (side === Side.LEFT) {
const itemsLeft = this.distributionTargetsLeft.length;
const itemsRight = this.distributionTargetsRight.length;
const itemsSelected = this.getSelectedItems(this.distributionTargetsLeft);
this.distributionTargetsSelected = this.distributionTargetsSelected.concat(itemsSelected);
this.distributionTargetsRight = this.distributionTargetsRight.concat(itemsSelected);
if (((itemsLeft - itemsSelected.length) <= TaskanaQueryParameters.pageSize) && ((itemsLeft + itemsRight) < this.page.totalElements)) {
this.getNextPage(side);
}
} else {
const itemsSelected = this.getSelectedItems(this.distributionTargetsRight);
this.distributionTargetsSelected = this.removeSelectedItems(this.distributionTargetsSelected, itemsSelected);
this.distributionTargetsRight = this.removeSelectedItems(this.distributionTargetsRight, itemsSelected);
this.distributionTargetsLeft = this.distributionTargetsLeft.concat(itemsSelected);
}
this.uncheckSelectAll(side);
}
onSave() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.updateWorkBasketsDistributionTargets(
this.distributionTargetsSelectedResource._links.self.href, this.getSeletedIds()
).subscribe(response => {
this.requestInProgressService.setRequestInProgress(false);
this.distributionTargetsSelected = response.distributionTargets;
this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected);
this.distributionTargetsClone = Object.assign([], this.distributionTargetsLeft);
this.notificationsService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_8,
new Map<string, string>([['workbasketName', this.workbasket.name]])
);
return true;
},
error => {
this.notificationsService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_3, error);
this.requestInProgressService.setRequestInProgress(false);
return false;
});
return false;
}
onClear() {
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
this.distributionTargetsLeft = Object.assign([], this.distributionTargetsClone);
this.distributionTargetsRight = Object.assign([], this.distributionTargetsSelectedClone);
this.distributionTargetsSelected = Object.assign([], this.distributionTargetsSelectedClone);
}
performFilter(dualListFilter: any) {
this.fillDistributionTargets(dualListFilter.side, undefined);
this.onRequest(false, dualListFilter.side);
this.workbasketFilterSubscription = 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)
.subscribe(resultList => {
this.fillDistributionTargets(dualListFilter.side, (resultList.workbaskets));
this.onRequest(true, dualListFilter.side);
});
}
ngOnDestroy(): void {
if (this.distributionTargetsSubscription) { this.distributionTargetsSubscription.unsubscribe(); }
if (this.workbasketSubscription) { this.workbasketSubscription.unsubscribe(); }
if (this.workbasketFilterSubscription) { this.workbasketFilterSubscription.unsubscribe(); }
if (this.savingDistributionTargetsSubscription) { this.savingDistributionTargetsSubscription.unsubscribe(); }
if (this.orientationSubscription) { this.orientationSubscription.unsubscribe(); }
}
private init() {
init() {
this.onRequest();
if (!this.workbasket._links.distributionTargets) {
return;
}
this.distributionTargetsSubscription = this.workbasketService.getWorkBasketsDistributionTargets(
this.workbasket._links.distributionTargets.href
).subscribe(
(distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource) => {
this.distributionTargetsSelectedResource = distributionTargetsSelectedResource;
this.distributionTargetsSelected = distributionTargetsSelectedResource.distributionTargets;
this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected);
TaskanaQueryParameters.page = 1;
this.calculateNumberItemsList();
this.getWorkbaskets();
}
);
this.savingDistributionTargetsSubscription = this.savingWorkbaskets.triggeredDistributionTargetsSaving()
this.store.dispatch(new GetWorkbasketDistributionTargets(this.workbasket._links.distributionTargets.href));
this.savingWorkbaskets.triggeredDistributionTargetsSaving()
.pipe(takeUntil(this.destroy$))
.subscribe((savingInformation: SavingInformation) => {
if (this.action === ACTION.COPY) {
this.distributionTargetsSelectedResource._links.self.href = savingInformation.url;
@ -192,47 +129,25 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
}
});
this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => {
this.calculateNumberItemsList();
this.getWorkbaskets();
});
this.orientationService.getOrientation()
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.calculateNumberItemsList();
this.getWorkbaskets();
});
}
private calculateNumberItemsList() {
if (this.panelBody) {
const cardHeight = 72;
const unusedHeight = 100;
this.cards = this.orientationService.calculateNumberItemsList(
this.panelBody.nativeElement.offsetHeight, cardHeight, unusedHeight, true
) + 1; // TODO: warum +1
}
}
private fillDistributionTargets(side: Side, workbaskets: WorkbasketSummary[]) {
this.distributionTargetsLeft = side === Side.LEFT ? workbaskets : this.distributionTargetsLeft;
this.distributionTargetsRight = side === Side.RIGHT ? workbaskets : this.distributionTargetsRight;
}
private getNextPage(side: Side) {
TaskanaQueryParameters.page += 1;
this.getWorkbaskets(side);
}
private getWorkbaskets(side?: Side) {
if (!this.distributionTargetsLeft) {
this.distributionTargetsLeft = [];
}
if (!this.distributionTargetsRight) {
this.distributionTargetsRight = [];
}
getWorkbaskets(side?: Side) {
if (this.distributionTargetsSelected && !this.initialized) {
this.initialized = true;
TaskanaQueryParameters.pageSize = this.cards + this.distributionTargetsSelected.length;
}
this.workbasketSubscription = this.workbasketService.getWorkBasketsSummary(true)
// TODO: Implement this into NGXS
this.workbasketService.getWorkBasketsSummary(true)
.pipe(takeUntil(this.destroy$))
.subscribe(
(distributionTargetsAvailable: WorkbasketSummaryResource) => {
(distributionTargetsAvailable: WorkbasketSummaryRepresentation) => {
if (TaskanaQueryParameters.page === 1) {
this.distributionTargetsLeft = [];
this.page = distributionTargetsAvailable.page;
@ -251,17 +166,121 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
);
}
private setBadge() {
performFilter(dualListFilter: any) {
this.fillDistributionTargets(dualListFilter.side, undefined);
this.onRequest(false, dualListFilter.side);
this.store.dispatch(new GetWorkbasketsSummary(true, '', '', '',
dualListFilter.filterBy.filterParams.name, dualListFilter.filterBy.filterParams.description, '',
dualListFilter.filterBy.filterParams.owner, dualListFilter.filterBy.filterParams.type, '',
dualListFilter.filterBy.filterParams.key, '', true)).subscribe((state: WorkbasketStateModel) => {
this.fillDistributionTargets(dualListFilter.side, state.paginatedWorkbasketsSummary.workbaskets);
this.onRequest(true, dualListFilter.side);
});
}
onSave() {
this.requestInProgressService.setRequestInProgress(true);
this.store.dispatch(new UpdateWorkbasketDistributionTargets(
this.distributionTargetsSelectedResource._links.self.href,
this.getSeletedIds()
)).subscribe(() => {
this.requestInProgressService.setRequestInProgress(false);
return true;
}, error => {
this.requestInProgressService.setRequestInProgress(false);
return false;
});
/* TODO: OLD IMPLEMENTATION, KEPT HERE FOR REFERENCE
this.workbasketService.updateWorkBasketsDistributionTargets(
this.distributionTargetsSelectedResource._links.self.href, this.getSeletedIds()
).subscribe(response => {
this.requestInProgressService.setRequestInProgress(false);
this.distributionTargetsSelected = response.distributionTargets;
this.distributionTargetsSelectedClone = Object.assign([], this.distributionTargetsSelected);
this.distributionTargetsClone = Object.assign([], this.distributionTargetsLeft);
this.notificationsService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_8,
new Map<string, string>([['workbasketName', this.workbasket.name]])
);
return true;
},
error => {
this.notificationsService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_3, error);
this.requestInProgressService.setRequestInProgress(false);
return false;
});
*/
return false;
}
moveDistributionTargets(side: number) {
if (side === Side.LEFT) {
const itemsLeft = this.distributionTargetsLeft.length;
const itemsRight = this.distributionTargetsRight.length;
const itemsSelected = this.getSelectedItems(this.distributionTargetsLeft);
this.distributionTargetsSelected = [...this.distributionTargetsSelected, ...itemsSelected];
this.distributionTargetsRight = this.distributionTargetsRight.concat(itemsSelected);
if (((itemsLeft - itemsSelected.length) <= TaskanaQueryParameters.pageSize) && ((itemsLeft + itemsRight) < this.page.totalElements)) {
this.getNextPage(side);
}
this.unselectItems(this.distributionTargetsSelected);
} else {
const itemsSelected = this.getSelectedItems(this.distributionTargetsRight);
this.distributionTargetsSelected = this.removeSelectedItems(this.distributionTargetsSelected, itemsSelected);
this.distributionTargetsRight = this.removeSelectedItems(this.distributionTargetsRight, itemsSelected);
this.distributionTargetsLeft = this.distributionTargetsLeft.concat(itemsSelected);
this.unselectItems(itemsSelected);
}
}
onClear() {
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
this.distributionTargetsLeft = Object.assign([], this.distributionTargetsClone);
this.distributionTargetsRight = Object.assign([], this.distributionTargetsSelectedClone);
this.distributionTargetsSelected = Object.assign([], this.distributionTargetsSelectedClone);
}
calculateNumberItemsList() {
if (this.panelBody) {
const cardHeight = 72;
const unusedHeight = 100;
this.cards = this.orientationService.calculateNumberItemsList(
this.panelBody.nativeElement.offsetHeight, cardHeight, unusedHeight, true
) + 1; // TODO: warum +1
}
}
fillDistributionTargets(side: Side, workbaskets: WorkbasketSummary[]) {
this.distributionTargetsLeft = side === Side.LEFT ? workbaskets : this.distributionTargetsLeft;
this.distributionTargetsRight = side === Side.RIGHT ? workbaskets : this.distributionTargetsRight;
}
getNextPage(side: Side) {
TaskanaQueryParameters.page += 1;
this.getWorkbaskets(side);
}
setBadge() {
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
}
private getSelectedItems(originList: any): Array<any> {
getSelectedItems(originList: any): Array<any> {
return originList.filter((item: any) => (item.selected === true));
}
private removeSelectedItems(originList: any, selectedItemList) {
unselectItems(originList: any): Array<any> {
// eslint-disable-next-line no-restricted-syntax
for (const item of originList) {
if (item.selected && item.selected === true) {
item.selected = false;
}
}
return originList;
}
removeSelectedItems(originList: any, selectedItemList) {
for (let index = originList.length - 1; index >= 0; index--) {
if (selectedItemList.some(itemToRemove => (originList[index].workbasketId === itemToRemove.workbasketId))) {
originList.splice(index, 1);
@ -270,7 +289,7 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
return originList;
}
private onRequest(finished: boolean = false, side?: Side) {
onRequest(finished: boolean = false, side?: Side) {
this.loadingItems = false;
const inProgress = !finished;
switch (side) {
@ -284,7 +303,7 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
}
}
private getSeletedIds(): Array<string> {
getSeletedIds(): Array<string> {
const distributionTargetsSelelected: Array<string> = [];
this.distributionTargetsSelected.forEach(item => {
distributionTargetsSelelected.push(item.workbasketId);
@ -296,4 +315,9 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
if (side === Side.LEFT && this.selectAllLeft) { this.selectAllLeft = false; }
if (side === Side.RIGHT && this.selectAllRight) { this.selectAllRight = false; }
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -1,44 +1,57 @@
<div id="dual-list-Left" class="dual-list list-left col-xs-12 col-md-5-6 container">
<div class="row header">
<div class="col-xs-2">
<button (click)="allSelected = !allSelected; selectAll(allSelected);" class="btn btn-default btn-sm no-style" title="Select all">
<span class="material-icons md-20 blue ">{{allSelected ? 'check_box': 'check_box_outline_blank'}}</span>
</button>
</div>
<div class="col-xs-7">
<h5>{{header}}</h5>
</div>
<div class="pull-right">
<button class="btn btn-default btn-sm" type="button" id="collapsedMenufilterWb" aria-expanded="false" (click)="changeToolbarState(!toolbarState)"
data-toggle="tooltip" title="Filter">
<span class="material-icons md-20 blue ">{{!toolbarState? 'search' : 'expand_less'}}</span>
</button>
</div>
</div>
<div [@toggleDown]="toolbarState">
<taskana-shared-filter (performFilter)="performAvailableFilter($event)"></taskana-shared-filter>
</div>
<taskana-shared-spinner [isRunning]="requestInProgress" positionClass="centered-spinner" class="floating"></taskana-shared-spinner>
<div infiniteScroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50" (scrolled)="onScroll()" [scrollWindow]="false" class="infinite-scroll">
<ul class="list-group">
<li class="list-group-item" *ngFor="let distributionTarget of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side"
[class.selected]="distributionTarget.selected" type="text" (click)="distributionTarget.selected = !distributionTarget.selected">
<div class="row">
<dl class="col-xs-1">
<taskana-administration-icon-type [type]="distributionTarget.type"></taskana-administration-icon-type>
</dl>
<dl class="col-xs-10">
<dt>{{distributionTarget.name}},
<i>{{distributionTarget.key}} </i>
</dt>
<dd>{{distributionTarget.description}} &nbsp;</dd>
<dd>{{distributionTarget.owner}} &nbsp;</dd>
</dl>
</div>
</li>
<li class="list-group-item" *ngIf="loadingItems">
<taskana-shared-spinner [isRunning]="loadingItems" positionClass="centered-spinner" class="floating"></taskana-shared-spinner>
</li>
</ul>
</div>
<!-- ACTION TOOLBAR -->
<div class="action-toolbar">
<div class="row header">
<div class="col-xs-2">
<button (click)="allSelected = !allSelected; selectAll(allSelected);"
class="btn btn-default btn-sm no-style" title="Select all">
<span class="material-icons md-20 blue ">{{allSelected ? 'check_box' : 'check_box_outline_blank'}}</span>
</button>
</div>
<div class="col-xs-7">
<h5>{{header}}</h5>
</div>
<div class="pull-right">
<button class="btn btn-default btn-sm" type="button" id="collapsedMenufilterWb" aria-expanded="false"
(click)="changeToolbarState(!toolbarState)"
data-toggle="tooltip" title="Filter">
<span class="material-icons md-20 blue ">{{!toolbarState ? 'search' : 'expand_less'}}</span>
</button>
</div>
</div>
<div [@toggleDown]="toolbarState">
<taskana-shared-filter (performFilter)="performAvailableFilter($event)"></taskana-shared-filter>
</div>
<taskana-shared-spinner [isRunning]="requestInProgress" positionClass="centered-spinner"
class="floating"></taskana-shared-spinner>
</div>
<!-- WORKBASKET LIST -->
<div infiniteScroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50" (scrolled)="onScroll()"
[scrollWindow]="false" class="infinite-scroll">
<ul class="list-group">
<li class="list-group-item"
*ngFor="let distributionTarget of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side"
[class.selected]="distributionTarget.selected" type="text"
(click)="distributionTarget.selected = !distributionTarget.selected">
<div class="row">
<dl class="col-xs-1">
<taskana-administration-icon-type [type]="distributionTarget.type"></taskana-administration-icon-type>
</dl>
<dl class="col-xs-10">
<dt>{{distributionTarget.name}},
<i>{{distributionTarget.key}} </i>
</dt>
<dd>{{distributionTarget.description}} &nbsp;</dd>
<dd>{{distributionTarget.owner}} &nbsp;</dd>
</dl>
</div>
</li>
<li class="list-group-item" *ngIf="loadingItems">
<taskana-shared-spinner [isRunning]="loadingItems" positionClass="centered-spinner"
class="floating"></taskana-shared-spinner>
</li>
</ul>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { Filter } from 'app/shared/models/filter';
import { expandDown } from 'theme/animations/expand.animation';
@ -11,8 +11,8 @@ import { Side } from '../workbasket-distribution-targets/workbasket-distribution
animations: [expandDown]
})
export class WorkbasketDualListComponent implements OnInit {
@Input() distributionTargets: Array<WorkbasketSummary>;
@Input() distributionTargetsSelected: Array<WorkbasketSummary>;
@Input() distributionTargets: WorkbasketSummary[];
@Input() distributionTargetsSelected: WorkbasketSummary[];
@Output() performDualListFilter = new EventEmitter<{ filterBy: Filter, side: Side }>();
@Input() requestInProgress = false;
@Input() loadingItems ? = false;

View File

@ -5,7 +5,7 @@
<button type="button" (click)="onSubmit()" data-toggle="tooltip" title="Save" class="btn btn-default btn-primary">
<span class="material-icons md-20">save</span>
</button>
<button type="button" (click)="onClear()" data-toggle="tooltip" title="Undo Changes" class="btn btn-default">
<button type="button" (click)="onUndo()" data-toggle="tooltip" title="Undo Changes" class="btn btn-default">
<span class="material-icons md-20 blue">undo</span>
</button>
<button type="button" (click)="removeDistributionTargets()" data-toggle="tooltip" title="Remove workbasket as distribution target"
@ -23,9 +23,11 @@
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div>
<div class="panel-body">
<form #WorkbasketForm="ngForm">
<div class="col-md-6">
<!-- KEY -->
<div class="form-group required">
<label for="wb-key" class="control-label">Key</label>
<input type="text" required #key="ngModel" class="form-control" id="wb-key" placeholder="Key"
@ -34,6 +36,8 @@
errorMessage="* Key is required">
</taskana-shared-field-error-display>
</div>
<!-- NAME -->
<div class="form-group required">
<label for="wb-name" class="control-label">Name</label>
<input type="text" required #name="ngModel" class="form-control" id="wb-name" placeholder="Name"
@ -42,11 +46,13 @@
errorMessage="* Name is required">
</taskana-shared-field-error-display>
</div>
<!-- OWNER -->
<div class="input-group form-group col-xs-12 required">
<label for="wb-owner" class="control-label ">Owner</label>
<taskana-shared-type-ahead *ngIf="(workbasketsCustomisation$ | async)?.information?.lookupField else ownerInput" required #owner="ngModel" name="workbasket.owner"
[(ngModel)]="workbasket.owner" placeHolderMessage="* Owner is required" [validationValue]="this.toogleValidationMap.get('workbasket.owner')"
[displayError]="!isFieldValid('workbasket.owner')" width="100%"></taskana-shared-type-ahead>
<taskana-shared-type-ahead *ngIf="lookupField else ownerInput" required #owner="ngModel" name="workbasket.owner"
[(ngModel)]="workbasket.owner" placeHolderMessage="* Owner is required" [validationValue]="this.toogleValidationMap.get('workbasket.owner')"
[displayError]="!isFieldValid('workbasket.owner')" width="100%"></taskana-shared-type-ahead>
<ng-template #ownerInput>
<input type="text" required #owner="ngModel" class="form-control" id="wb-owner" placeholder="Owner"
[(ngModel)]="workbasket.owner" name="workbasket.owner">
@ -55,11 +61,15 @@
</taskana-shared-field-error-display>
</ng-template>
</div>
<!-- DOMAIN -->
<div class="form-group ">
<label for="wb-domain" class="control-label">Domain</label>
<input type="text" #domain="ngModel" class="form-control" disabled id="wb-domain" placeholder="Domain"
[(ngModel)]="workbasket.domain" name="workbasket.domain">
</div>
<!-- TYPE & DESCRIPTION-->
<div class="row">
<div class="form-group col-xs-4">
<label class="control-label">Type</label>
@ -97,7 +107,7 @@
<input type="text" class="form-control" id="wb-org-level-2" placeholder="OrgLevel 2" [(ngModel)]="workbasket.orgLevel2"
name="workbasket.orgLevel2">
</div>
<div class="form-group">
<div class="form-group" style="padding-top: 18px;">
<label for="wb-org-level-3" class="control-label">OrgLevel 3</label>
<input type="text" class="form-control" id="wb-org-level-3" placeholder="OrgLevel 3" [(ngModel)]="workbasket.orgLevel3"
name="workbasket.orgLevel3">

View File

@ -4,20 +4,18 @@ import { FormsModule } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { Component } from '@angular/core';
import { Routes } from '@angular/router';
import { Workbasket } from 'app/shared/models/workbasket';
import { ICONTYPES } from 'app/shared/models/icon-types';
import { ACTION } from 'app/shared/models/action';
import { Links } from 'app/shared/models/links';
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { configureTests } from 'app/app.test.configuration';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { NgxsModule, Store } from '@ngxs/store';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { NgxsModule } from '@ngxs/store';
import { WorkbasketInformationComponent } from './workbasket-information.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
@ -43,29 +41,50 @@ describe('WorkbasketInformationComponent', () => {
let requestInProgressService;
let formsValidatorService;
const storeSpy: jasmine.SpyObj<Store> = jasmine.createSpyObj('Store', ['select']);
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [WorkbasketInformationComponent, DummyDetailComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, RouterTestingModule.withRoutes(routes), NgxsModule.forRoot()],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, RouterTestingModule.withRoutes(routes),
NgxsModule.forRoot()],
providers: [WorkbasketService, NotificationService, SavingWorkbasketService,
RequestInProgressService, FormsValidatorService, { provide: Store, useValue: storeSpy }]
RequestInProgressService, FormsValidatorService]
});
};
function createWorkbasket(workbasketId?, created?, key?, domain?, type?, modified?, name?, description?,
owner?, custom1?, custom2?, custom3?, custom4?, orgLevel1?, orgLevel2?, orgLevel3?, orgLevel4?,
_links?: Links, markedForDeletion?: boolean) {
if (!type) {
// eslint-disable-next-line no-param-reassign
type = 'PERSONAL';
}
const workbasket: Workbasket = {
workbasketId,
created,
key,
domain,
type,
modified,
name,
description,
owner,
custom1,
custom2,
custom3,
custom4,
orgLevel1,
orgLevel2,
orgLevel3,
orgLevel4,
markedForDeletion,
_links
};
return workbasket;
}
beforeEach(done => {
configureTests(configure).then(testBed => {
storeSpy.select.and.callFake(selector => {
switch (selector) {
case EngineConfigurationSelectors.workbasketsCustomisation:
return of({ information: {} });
default:
return of();
}
});
fixture = testBed.createComponent(WorkbasketInformationComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
@ -83,6 +102,7 @@ describe('WorkbasketInformationComponent', () => {
});
afterEach(() => {
fixture.destroy();
document.body.removeChild(debugElement);
});
@ -90,21 +110,8 @@ describe('WorkbasketInformationComponent', () => {
expect(component).toBeTruthy();
});
it('should create a panel with heading and form with all fields', async(() => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
'modified', 'name', 'description', 'owner', 'custom1', 'custom2', 'custom3', 'custom4',
'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4', null);
fixture.detectChanges();
expect(debugElement.querySelector('#wb-information')).toBeDefined();
expect(debugElement.querySelector('#wb-information > .panel-heading > h4').textContent.trim()).toBe('name');
expect(debugElement.querySelectorAll('#wb-information > .panel-body > form').length).toBe(1);
fixture.whenStable().then(() => {
expect(debugElement.querySelector('#wb-information > .panel-body > form > div > div > input ').value).toBe('keyModified');
});
}));
it('selectType should set workbasket.type to personal with 0 and group in other case', () => {
component.workbasket = new Workbasket('id1');
component.workbasket = createWorkbasket('id1');
expect(component.workbasket.type).toEqual('PERSONAL');
component.selectType(ICONTYPES.GROUP);
expect(component.workbasket.type).toEqual('GROUP');
@ -112,7 +119,7 @@ describe('WorkbasketInformationComponent', () => {
it('should create a copy of workbasket when workbasket is selected', () => {
expect(component.workbasketClone).toBeUndefined();
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
component.workbasket = createWorkbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4');
component.ngOnChanges(
undefined
@ -120,95 +127,4 @@ describe('WorkbasketInformationComponent', () => {
fixture.detectChanges();
expect(component.workbasket.workbasketId).toEqual(component.workbasketClone.workbasketId);
});
it('should reset requestInProgress after saving request is done', async(() => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', { self: { href: 'someUrl' } });
fixture.detectChanges();
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(of(component.workbasket));
component.onSubmit();
expect(component.requestInProgress).toBeFalsy();
}));
it('should trigger triggerWorkBasketSaved method after saving request is done', async(() => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', { self: { href: 'someurl' } });
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(of(component.workbasket));
fixture.detectChanges();
spyOn(formsValidatorService, 'validateFormAccess').and.returnValue(Promise.resolve(true));
component.onSubmit();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled();
});
}));
it('should post a new workbasket when no workbasketId is defined and update workbasket', async(() => {
component.workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', {});
spyOn(workbasketService, 'createWorkbasket').and.returnValue(of(
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', {})
));
fixture.detectChanges();
spyOn(formsValidatorService, 'validateFormAccess').and.returnValue(Promise.resolve(true));
component.onSubmit();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(alertService.showToast).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId');
});
}));
it('should post a new workbasket, new distribution targets and new access '
+ 'items when no workbasketId is defined and action is copy', async(() => {
component.workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
'modified', 'name', 'description', 'owner', 'custom1', 'custom2',
'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', {});
component.action = ACTION.COPY;
spyOn(workbasketService, 'createWorkbasket').and.returnValue(of(
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', { distributionTargets: { href: 'someurl' }, accessItems: { href: 'someurl' } })
));
spyOn(savingWorkbasketService, 'triggerDistributionTargetSaving');
spyOn(savingWorkbasketService, 'triggerAccessItemsSaving');
fixture.detectChanges();
spyOn(formsValidatorService, 'validateFormAccess').and.returnValue(Promise.resolve(true));
component.onSubmit();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(alertService.showToast).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId');
expect(savingWorkbasketService.triggerDistributionTargetSaving).toHaveBeenCalled();
expect(savingWorkbasketService.triggerAccessItemsSaving).toHaveBeenCalled();
});
}));
it('should trigger requestInProgress service true before and requestInProgress false after remove a workbasket', () => {
component.workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
'modified', 'name', 'description', 'owner', 'custom1', 'custom2',
'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', { removeDistributionTargets: { href: 'someurl' } });
spyOn(workbasketService, 'removeDistributionTarget').and.returnValue(of(undefined));
const requestInProgressServiceSpy = spyOn(requestInProgressService, 'setRequestInProgress');
component.removeDistributionTargets();
expect(requestInProgressServiceSpy).toHaveBeenCalledWith(true);
workbasketService.removeDistributionTarget().subscribe(() => {
}, () => { }, () => {
expect(requestInProgressServiceSpy).toHaveBeenCalledWith(false);
});
});
});

View File

@ -6,9 +6,9 @@ import { Component,
SimpleChanges,
ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subscription } from 'rxjs';
import { Observable, Subject, Subscription } from 'rxjs';
import { NgForm } from '@angular/forms';
import { Select } from '@ngxs/store';
import { Select, Store } from '@ngxs/store';
import { ICONTYPES } from 'app/shared/models/icon-types';
import { ACTION } from 'app/shared/models/action';
@ -19,13 +19,16 @@ import { SavingWorkbasketService, SavingInformation } from 'app/administration/s
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { map } from 'rxjs/operators';
import { map, takeUntil } from 'rxjs/operators';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { CustomField,
getCustomFields,
WorkbasketsCustomisation } from '../../../shared/models/customisation';
import { CopyWorkbasket, MarkWorkbasketForDeletion,
RemoveDistributionTarget, SaveNewWorkbasket,
UpdateWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions';
@Component({
selector: 'taskana-administration-workbasket-information',
@ -37,24 +40,24 @@ implements OnInit, OnChanges, OnDestroy {
@Input()
workbasket: Workbasket;
workbasketClone: Workbasket;
workbasketErrors;
@Input()
action: ACTION;
@ViewChild('WorkbasketForm', { static: false })
workbasketForm: NgForm;
workbasketClone: Workbasket;
allTypes: Map<string, string>;
requestInProgress = false;
badgeMessage = '';
@Select(EngineConfigurationSelectors.workbasketsCustomisation) workbasketsCustomisation$: Observable<WorkbasketsCustomisation>;
customFields$: Observable<CustomField[]>;
toogleValidationMap = new Map<string, boolean>();
lookupField = false;
private workbasketSubscription: Subscription;
private routeSubscription: Subscription;
@ViewChild('WorkbasketForm', { static: false })
workbasketForm: NgForm;
@Select(EngineConfigurationSelectors.workbasketsCustomisation)
workbasketsCustomisation$: Observable<WorkbasketsCustomisation>;
customFields$: Observable<CustomField[]>;
destroy$ = new Subject<void>();
constructor(
private workbasketService: WorkbasketService,
@ -63,11 +66,12 @@ implements OnInit, OnChanges, OnDestroy {
private savingWorkbasket: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService,
private formsValidatorService: FormsValidatorService,
private notificationService: NotificationService
private notificationService: NotificationService,
private store: Store
) {
}
ngOnInit(): void {
ngOnInit() {
this.allTypes = new Map([
['PERSONAL', 'Personal'],
['GROUP', 'Group'],
@ -78,9 +82,16 @@ implements OnInit, OnChanges, OnDestroy {
map(customisation => customisation.information),
getCustomFields(customFieldCount)
);
this.workbasketsCustomisation$
.pipe(takeUntil(this.destroy$))
.subscribe(workbasketsCustomization => {
if (workbasketsCustomization.information.owner) {
this.lookupField = workbasketsCustomization.information.owner.lookupField;
}
});
}
ngOnChanges(changes: SimpleChanges): void {
ngOnChanges(changes: SimpleChanges) {
this.workbasketClone = { ...this.workbasket };
if (this.action === ACTION.CREATE) {
this.badgeMessage = 'Creating new workbasket';
@ -108,7 +119,7 @@ implements OnInit, OnChanges, OnDestroy {
return this.formsValidatorService.isFieldValid(this.workbasketForm, field);
}
onClear() {
onUndo() {
this.formsValidatorService.formSubmitAttempt = false;
this.notificationService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
this.workbasket = { ...this.workbasketClone };
@ -122,32 +133,11 @@ implements OnInit, OnChanges, OnDestroy {
}
copyWorkbasket() {
this.router.navigate([{ outlets: { detail: ['copy-workbasket'] } }], {
relativeTo: this.route.parent
});
this.store.dispatch(new CopyWorkbasket(this.workbasket));
}
removeDistributionTargets() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService
.removeDistributionTarget(
this.workbasket._links.removeDistributionTargets.href
)
.subscribe(
reponse => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_9,
new Map<string, string>([['workbasketId', this.workbasket.workbasketId]])
);
},
error => {
this.notificationService.triggerError(NOTIFICATION_TYPES.REMOVE_ERR_2,
error,
new Map<String, String>([['workbasketId', this.workbasket.workbasketId]]));
this.requestInProgressService.setRequestInProgress(false);
}
);
this.store.dispatch(new RemoveDistributionTarget(this.workbasket._links.removeDistributionTargets.href));
}
private onSave() {
@ -156,24 +146,11 @@ implements OnInit, OnChanges, OnDestroy {
this.postNewWorkbasket();
return;
}
this.workbasketSubscription = this.workbasketService
.updateWorkbasket(this.workbasket._links.self.href, this.workbasket)
.subscribe(
workbasketUpdated => {
this.afterRequest();
this.workbasket = workbasketUpdated;
this.workbasketClone = { ...this.workbasket };
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_10,
new Map<string, string>([['workbasketKey', workbasketUpdated.key]])
);
},
error => {
this.afterRequest();
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_4, error);
}
);
this.store.dispatch(new UpdateWorkbasket(this.workbasket._links.self.href, this.workbasket))
.subscribe(state => {
this.requestInProgressService.setRequestInProgress(false);
this.workbasketClone = { ...this.workbasket };
});
}
private beforeRequest() {
@ -187,19 +164,9 @@ implements OnInit, OnChanges, OnDestroy {
private postNewWorkbasket() {
this.addDateToWorkbasket();
this.workbasketService.createWorkbasket(this.workbasket).subscribe(
(workbasketUpdated: Workbasket) => {
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_11,
new Map<string, string>([['workbasketKey', workbasketUpdated.key]])
);
this.workbasket = workbasketUpdated;
this.store.dispatch(new SaveNewWorkbasket(this.workbasket)).subscribe(
() => {
this.afterRequest();
this.workbasketService.triggerWorkBasketSaved();
this.workbasketService.selectWorkBasket(this.workbasket.workbasketId);
this.router.navigate([`../${this.workbasket.workbasketId}`], {
relativeTo: this.route
});
if (this.action === ACTION.COPY) {
this.savingWorkbasket.triggerDistributionTargetSaving(
new SavingInformation(
@ -214,10 +181,6 @@ implements OnInit, OnChanges, OnDestroy {
)
);
}
},
error => {
this.notificationService.triggerError(NOTIFICATION_TYPES.CREATE_ERR_2, error);
this.requestInProgressService.setRequestInProgress(false);
}
);
}
@ -229,38 +192,18 @@ implements OnInit, OnChanges, OnDestroy {
}
private onRemoveConfirmed() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService
.markWorkbasketForDeletion(this.workbasket._links.self.href)
.subscribe(
response => {
this.requestInProgressService.setRequestInProgress(false);
this.workbasketService.triggerWorkBasketSaved();
if (response.status === 202) {
this.notificationService.triggerError(NOTIFICATION_TYPES.MARK_ERR,
undefined,
new Map<String, String>([['workbasketId', this.workbasket.workbasketId]]));
} else {
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_12,
new Map<string, string>([['workbasketId', this.workbasket.workbasketId]])
);
}
this.router.navigate(['taskana/administration/workbaskets']);
}
);
}
ngOnDestroy() {
if (this.workbasketSubscription) {
this.workbasketSubscription.unsubscribe();
}
if (this.routeSubscription) {
this.routeSubscription.unsubscribe();
}
this.beforeRequest();
this.store.dispatch(new MarkWorkbasketForDeletion(this.workbasket._links.self.href)).subscribe(() => {
this.afterRequest();
});
}
getWorkbasketCustomProperty(custom: number) {
return `custom${custom}`;
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -9,8 +9,6 @@ import { HttpClientModule } from '@angular/common/http';
import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from 'app/shared/shared.module';
import { AppModule } from 'app/app.module';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { Links } from 'app/shared/models/links';
import { Filter } from 'app/shared/models/filter';
import { Sorting } from 'app/shared/models/sorting';
@ -23,6 +21,7 @@ import { WorkbasketDefinitionService } from 'app/administration/services/workbas
import { configureTests } from 'app/app.test.configuration';
import { ImportExportService } from 'app/administration/services/import-export.service';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar.component';
import { ICONTYPES } from '../../../shared/models/icon-types';
@Component({
selector: 'taskana-dummy-detail',
@ -58,17 +57,20 @@ describe('WorkbasketListToolbarComponent', () => {
});
};
configureTests(configure).then(testBed => {
fixture = testBed.createComponent(WorkbasketListToolbarComponent);
workbasketService = testBed.get(WorkbasketService);
router = testBed.get(Router);
fixture = TestBed.createComponent(WorkbasketListToolbarComponent);
workbasketService = TestBed.get(WorkbasketService);
router = TestBed.get(Router);
spyOn(workbasketService, 'markWorkbasketForDeletion').and.returnValue(of(''));
spyOn(workbasketService, 'triggerWorkBasketSaved');
debugElement = fixture.debugElement.nativeElement;
component = fixture.componentInstance;
component.workbaskets = [
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1')
];
component.workbaskets = [{ workbasketId: '1',
key: 'key1',
name: 'NAME1',
description: 'description 1',
owner: 'owner 1',
type: ICONTYPES.PERSONAL }];
component.workbaskets[0].markedForDeletion = false;
fixture.detectChanges();
@ -84,12 +86,6 @@ describe('WorkbasketListToolbarComponent', () => {
expect(component).toBeTruthy();
});
it('should navigate to new-workbasket when click on add new workbasket', () => {
const spy = spyOn(router, 'navigate');
component.addWorkbasket();
expect(spy.calls.first().args[0][0].outlets.detail[0]).toBe('new-workbasket');
});
it('should emit performSorting when sorting is triggered', () => {
let sort: Sorting;
const compareSort = new Sorting();

View File

@ -3,14 +3,18 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Sorting } from 'app/shared/models/sorting';
import { Filter } from 'app/shared/models/filter';
import { Subscription } from 'rxjs';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { TaskanaType } from 'app/shared/models/taskana-type';
import { expandDown } from 'theme/animations/expand.animation';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { Select, Store } from '@ngxs/store';
import { Location } from '@angular/common';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ACTION } from '../../../shared/models/action';
import { CreateWorkbasket, SetActiveAction } from '../../../shared/store/workbasket-store/workbasket.actions';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
@Component({
selector: 'taskana-administration-workbasket-list-toolbar',
@ -23,7 +27,7 @@ export class WorkbasketListToolbarComponent implements OnInit {
@Input() workbasketDefaultSortBy: string;
@Output() performSorting = new EventEmitter<Sorting>();
@Output() performFilter = new EventEmitter<Filter>();
workbasketServiceSubscription: Subscription;
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'],
@ -33,15 +37,27 @@ export class WorkbasketListToolbarComponent implements OnInit {
toolbarState = false;
filterType = TaskanaType.WORKBASKETS;
@Select(WorkbasketSelectors.workbasketActiveAction)
workbasketActiveAction$: Observable<ACTION>;
destroy$ = new Subject<void>();
action: ACTION;
constructor(
private workbasketService: WorkbasketService,
private route: ActivatedRoute,
private router: Router,
private errors: NotificationService
private store: Store,
private location: Location
) {
}
ngOnInit() {
this.workbasketActiveAction$
.pipe(takeUntil(this.destroy$))
.subscribe(action => {
this.action = action;
});
}
sorting(sort: Sorting) {
@ -53,7 +69,16 @@ export class WorkbasketListToolbarComponent implements OnInit {
}
addWorkbasket() {
this.workbasketService.selectWorkBasket();
this.router.navigate([{ outlets: { detail: ['new-workbasket'] } }], { relativeTo: this.route });
// this.store.dispatch(new SetActiveAction(ACTION.CREATE));
if (this.action !== ACTION.CREATE) {
this.store.dispatch(new CreateWorkbasket());
}
// this.workbasketService.selectWorkBasket();
// this.router.navigate([{ outlets: { detail: ['new-workbasket'] } }], { relativeTo: this.route });
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -1,15 +1,17 @@
<div class="footer-space-pagination-list">
<div #wbToolbar>
<taskana-administration-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)"
(performSorting)="performSorting($event)" (importSucessful)="refreshWorkbasketList()" [workbasketDefaultSortBy]="workbasketDefaultSortBy"></taskana-administration-workbasket-list-toolbar>
<taskana-administration-workbasket-list-toolbar [workbaskets]="workbasketsSummary$ | async" (performFilter)="performFilter($event)"
(performSorting)="performSorting($event)" [workbasketDefaultSortBy]="workbasketDefaultSortBy">
</taskana-administration-workbasket-list-toolbar>
</div>
<div *ngIf="(workbaskets && workbaskets.length > 0) else empty_workbaskets">
<div *ngIf="((workbasketsSummary$ | async) && (workbasketsSummary$ | async)?.length > 0) else empty_workbaskets">
<ul #wbList id="wb-list-container" class="list-group">
<li class="list-group-item no-space">
<div class="row"></div>
</li>
<li class="list-group-item" *ngFor="let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId"
type="text" (click)="selectWorkbasket(workbasket.workbasketId)">
<li class="list-group-item" *ngFor="let workbasket of (workbasketsSummary$ | async)"
[class.active]="workbasket.workbasketId == selectedId"
type="text" (click)="selectWorkbasket(workbasket.workbasketId)">
<div class="row">
<dl class="col-xs-1">
<taskana-administration-icon-type class="vertical-align" [type]="workbasket.type" tooltip="true" [selected]="workbasket.workbasketId === selectedId"></taskana-administration-icon-type>
@ -36,5 +38,9 @@
</div>
</ng-template>
</div>
<taskana-shared-pagination [(page)]="workbasketsResource ? workbasketsResource.page : workbasketsResource"
[type]="type" [numberOfItems]="workbaskets.length" (changePage)="changePage($event)"></taskana-shared-pagination>
<taskana-shared-pagination
[page]="(workbasketsSummaryRepresentation$ | async) ? (workbasketsSummaryRepresentation$ | async)?.page : (workbasketsSummaryRepresentation$ | async)"
[type]="type"
[numberOfItems]="(workbasketsSummary$ | async)?.length"
(changePage)="changePage($event)">
</taskana-shared-pagination>

View File

@ -8,8 +8,8 @@ import { Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { WorkbasketSummaryResource } from 'app/shared/models/workbasket-summary-resource';
import { Filter } from 'app/shared/models/filter';
import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation';
import { LinksWorkbasketSummary } from 'app/shared/models/links-workbasket-summary';
import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component';
@ -20,6 +20,7 @@ import { OrientationService } from 'app/shared/services/orientation/orientation.
import { configureTests } from 'app/app.test.configuration';
import { Page } from 'app/shared/models/page';
import { ImportExportService } from 'app/administration/services/import-export.service';
import { NgxsModule } from '@ngxs/store';
import { WorkbasketListToolbarComponent } from '../workbasket-list-toolbar/workbasket-list-toolbar.component';
import { WorkbasketListComponent } from './workbasket-list.component';
@ -31,7 +32,7 @@ class DummyDetailComponent {
}
@Component({
selector: 'taskana-shared-pagination',
selector: 'taskana-pagination',
template: 'dummydetail'
})
class PaginationComponent {
@ -44,13 +45,32 @@ class PaginationComponent {
@Output() changePage = new EventEmitter<any>();
}
const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', '')
),
{}
);
function createWorkbasketSummary(workbasketId, key, name, domain, type, description, owner, custom1, custom2, custom3, custom4) {
const workbasketSummary: WorkbasketSummary = {
workbasketId,
key,
name,
domain,
type,
description,
owner,
custom1,
custom2,
custom3,
custom4
};
return workbasketSummary;
}
const workbasketSummaryResource: WorkbasketSummaryRepresentation = {
workbaskets: [
createWorkbasketSummary('1', 'key1', 'NAME1', '', 'PERSONAL',
'description 1', 'owner1', '', '', '', ''),
createWorkbasketSummary('2', 'key2', 'NAME2', '', 'PERSONAL',
'description 2', 'owner2', '', '', '', ''),
],
_links: new LinksWorkbasketSummary({ href: 'url' }),
page: {}
};
describe('WorkbasketListComponent', () => {
let component: WorkbasketListComponent;
@ -71,12 +91,13 @@ describe('WorkbasketListComponent', () => {
WorkbasketListComponent,
DummyDetailComponent,
WorkbasketListToolbarComponent,
ImportExportComponent
ImportExportComponent,
],
imports: [
AngularSvgIconModule,
HttpClientModule,
RouterTestingModule.withRoutes(routes)
RouterTestingModule.withRoutes(routes),
NgxsModule.forRoot()
],
providers: [
WorkbasketService,
@ -90,6 +111,13 @@ describe('WorkbasketListComponent', () => {
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(WorkbasketListComponent);
component = fixture.componentInstance;
Object.defineProperty(component, 'workbasketsSummaryRepresentation$', { writable: true });
const page = {
workbaskets: [],
_links: {},
page: new Page(6, 3, 3, 1)
};
component.workbasketsSummaryRepresentation$ = of(page);
debugElement = fixture.debugElement.nativeElement;
workbasketService = TestBed.get(WorkbasketService);
const orientationService = TestBed.get(OrientationService);
@ -111,14 +139,6 @@ describe('WorkbasketListComponent', () => {
expect(component).toBeTruthy();
});
it('should call workbasketService.getWorkbasketsSummary method on init', () => {
component.ngOnInit();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalled();
workbasketService.getWorkBasketsSummary().subscribe(value => {
expect(value).toBe(workbasketSummaryResource);
});
});
it('should have wb-action-toolbar, wb-search-bar, wb-list-container, wb-pagination,'
+ ' collapsedMenufilterWb and taskana-filter created in the html', () => {
expect(debugElement.querySelector('#wb-action-toolbar')).toBeDefined();
@ -127,27 +147,6 @@ describe('WorkbasketListComponent', () => {
expect(debugElement.querySelector('#wb-list-container')).toBeDefined();
expect(debugElement.querySelector('#collapsedMenufilterWb')).toBeDefined();
expect(debugElement.querySelector('taskana-filter')).toBeDefined();
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
});
// it('should have two workbasketsummary rows created with the second one selected.', fakeAsync(() => {
// tick(0);
// fixture.detectChanges();
// fixture.whenStable().then(() => {
// expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
// expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class'))
// .toBe('list-group-item ng-star-inserted');
// expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class'))
// .toBe('list-group-item ng-star-inserted active');
// })
//
// }));
it('should have two workbasketsummary rows created with two different icons: user and users', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li')[1]
.querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/user.svg');
expect(debugElement.querySelectorAll('#wb-list-container > li')[2]
.querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/users.svg');
});
it('should have rendered sort by: name, id, description, owner and type', () => {
@ -157,18 +156,4 @@ describe('WorkbasketListComponent', () => {
expect(debugElement.querySelector('#sort-by-owner')).toBeDefined();
expect(debugElement.querySelector('#sort-by-type')).toBeDefined();
});
it('should have performRequest with forced = true after performFilter is triggered', (() => {
const filter = new Filter({
name: 'someName',
owner: 'someOwner',
description: 'someDescription',
key: 'someKey',
type: 'PERSONAL'
});
component.performFilter(filter);
expect(workbasketSummarySpy.calls.all()[1].args).toEqual([true, 'name', 'asc',
'', 'someName', 'someDescription', '', 'someOwner', 'PERSONAL', '', 'someKey', '']);
}));
});

View File

@ -1,8 +1,7 @@
import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { Observable, pipe, Subject, Subscription } from 'rxjs';
import { WorkbasketSummaryResource } from 'app/shared/models/workbasket-summary-resource';
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';
@ -12,6 +11,12 @@ import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.ser
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';
import { DeselectWorkbasket, GetWorkbasketsSummary,
SelectWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { Workbasket } from '../../../shared/models/workbasket';
@Component({
selector: 'taskana-administration-workbasket-list',
@ -20,15 +25,11 @@ import { ImportExportService } from 'app/administration/services/import-export.s
})
export class WorkbasketListComponent implements OnInit, OnDestroy {
selectedId = '';
workbasketsResource: WorkbasketSummaryResource;
workbaskets: Array<WorkbasketSummary> = [];
requestInProgress = false;
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: '' });
@ -36,47 +37,75 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
@ViewChild('wbToolbar', { static: true })
private toolbarElement: ElementRef;
private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription;
private workbasketServiceSavedSubscription: Subscription;
private orientationSubscription: Subscription;
private importingExportingSubscription: Subscription;
@Select(WorkbasketSelectors.workbasketsSummary)
workbasketsSummary$: Observable<WorkbasketSummary[]>;
@Select(WorkbasketSelectors.workbasketsSummaryRepresentation)
workbasketsSummaryRepresentation$: Observable<WorkbasketSummaryRepresentation>;
@Select(WorkbasketSelectors.selectedWorkbasket)
selectedWorkbasket$: Observable<Workbasket>;
destroy$ = new Subject<void>();
constructor(
private store: Store,
private workbasketService: WorkbasketService,
private router: Router,
private route: ActivatedRoute,
private orientationService: OrientationService,
private importExportService: ImportExportService
private importExportService: ImportExportService,
private ngxsActions$: Actions,
) {
this.ngxsActions$.pipe(ofActionDispatched(GetWorkbasketsSummary),
takeUntil(this.destroy$))
.subscribe(() => {
this.requestInProgress = true;
});
this.ngxsActions$.pipe(ofActionCompleted(GetWorkbasketsSummary),
takeUntil(this.destroy$))
.subscribe(() => {
this.requestInProgress = false;
});
}
ngOnInit() {
this.requestInProgress = true;
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
// TODO should be done in a different way.
setTimeout(() => {
this.selectedId = workbasketIdSelected;
}, 0);
});
this.selectedWorkbasket$.pipe(takeUntil(this.destroy$))
.subscribe(selectedWorkbasket => {
if (typeof selectedWorkbasket !== 'undefined') {
this.selectedId = selectedWorkbasket.workbasketId;
} else {
this.selectedId = undefined;
}
});
TaskanaQueryParameters.page = this.pageSelected;
TaskanaQueryParameters.pageSize = this.pageSize;
this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => {
this.performRequest();
});
this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => {
this.refreshWorkbasketList();
});
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => {
this.refreshWorkbasketList();
});
this.workbasketService.workbasketSavedTriggered()
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
this.performRequest();
});
this.orientationService.getOrientation()
.pipe(takeUntil(this.destroy$))
.subscribe((orientation: Orientation) => {
this.refreshWorkbasketList();
});
this.importExportService.getImportingFinished()
.pipe(takeUntil(this.destroy$))
.subscribe((value: Boolean) => {
this.refreshWorkbasketList();
});
}
selectWorkbasket(id: string) {
this.selectedId = id;
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
if (this.selectedId === id) {
this.store.dispatch(new DeselectWorkbasket());
} else {
this.store.dispatch(new SelectWorkbasket(id));
}
}
performSorting(sort: Sorting) {
@ -102,37 +131,14 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
}
private performRequest(): void {
TaskanaQueryParameters.pageSize = this.cards;
this.requestInProgress = true;
this.workbaskets = [];
this.workbasketServiceSubscription = this.workbasketService.getWorkBasketsSummary(
true, this.sort.sortBy, this.sort.sortDirection, '',
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(resultList => {
this.workbasketsResource = resultList;
this.workbaskets = resultList.workbaskets;
this.requestInProgress = false;
});
this.filterBy.filterParams.type, '', this.filterBy.filterParams.key, ''));
TaskanaQueryParameters.pageSize = this.cards;
}
ngOnDestroy() {
if (this.workBasketSummarySubscription) {
this.workBasketSummarySubscription.unsubscribe();
}
if (this.workbasketServiceSubscription) {
this.workbasketServiceSubscription.unsubscribe();
}
if (this.workbasketServiceSavedSubscription) {
this.workbasketServiceSavedSubscription.unsubscribe();
}
if (this.orientationSubscription) {
this.orientationSubscription.unsubscribe();
}
if (this.importingExportingSubscription) {
this.importingExportingSubscription.unsubscribe();
}
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -0,0 +1,17 @@
<div class="no-gutter">
<div class="col-xs-12 col-md-4 vertical-right-divider">
<taskana-administration-workbasket-list></taskana-administration-workbasket-list>
</div>
<div class="col-xs-12 col-md-8" *ngIf="showDetail; else showEmptyPage">
<taskana-administration-workbasket-details></taskana-administration-workbasket-details>
</div>
<ng-template #showEmptyPage>
<div class="hidden-xs hidden-sm col-md-8 container-no-items">
<div class="center-block no-detail">
<h3 class="grey">Select a workbasket</h3>
<svg-icon class="img-responsive empty-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
</div>
</div>
</ng-template>
</div>

View File

@ -0,0 +1,75 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Routes } from '@angular/router';
import { NgxsModule } from '@ngxs/store';
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { Location } from '@angular/common';
import { take } from 'rxjs/operators';
import { debug } from 'util';
import { WorkbasketOverviewComponent } from './workbasket-overview.component';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
}
@Component({
selector: 'taskana-workbasket-list',
template: 'dummylist'
})
export class DummyListComponent {
}
describe('WorkbasketOverviewComponent', () => {
let debugElement: any;
let component: WorkbasketOverviewComponent;
let fixture: ComponentFixture<WorkbasketOverviewComponent>;
const locationSpy: jasmine.SpyObj<Location> = jasmine.createSpyObj('Location', ['go']);
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent }
];
beforeEach(async(() => {
TestBed.configureTestingModule(
{ imports: [
RouterTestingModule.withRoutes(routes),
NgxsModule.forRoot()
],
declarations: [
WorkbasketOverviewComponent,
DummyDetailComponent,
DummyListComponent],
providers: [
{ provide: Location, useValue: locationSpy },
],
schemas: [NO_ERRORS_SCHEMA] }
)
.compileComponents();
}));
afterEach(() => {
fixture.destroy();
});
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketOverviewComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should not display detail component if showDetail is false', () => {
component.showDetail = false;
expect(debugElement.querySelector('taskana-administration-workbasket-details')).toBeNull();
});
it('should display detail component if showDetail is true', () => {
component.showDetail = true;
fixture.detectChanges();
expect(debugElement.querySelector('taskana-administration-workbasket-details')).toBeTruthy();
});
});

View File

@ -0,0 +1,57 @@
import { Component, OnInit } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { takeUntil } from 'rxjs/operators';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { CreateWorkbasket,
SelectWorkbasket,
SetActiveAction } from '../../../shared/store/workbasket-store/workbasket.actions';
@Component({
selector: 'app-workbasket-overview',
templateUrl: './workbasket-overview.component.html',
styleUrls: ['./workbasket-overview.component.scss']
})
export class WorkbasketOverviewComponent implements OnInit {
showDetail = false;
@Select(WorkbasketSelectors.selectedWorkbasketAndAction) selectedWorkbasketAndAction$: Observable<any>;
destroy$ = new Subject<void>();
routerParams: any;
constructor(
private route: ActivatedRoute,
private store: Store
) {
}
ngOnInit() {
if (this.route.firstChild) {
this.route.firstChild.params
.pipe(takeUntil(this.destroy$))
.subscribe(params => {
this.routerParams = params;
if (this.routerParams.id) {
this.showDetail = true;
if (this.routerParams.id === 'new-workbasket') {
this.store.dispatch(new CreateWorkbasket());
} else {
this.store.dispatch(new SelectWorkbasket(this.routerParams.id));
}
}
});
}
this.selectedWorkbasketAndAction$
.pipe(takeUntil(this.destroy$))
.subscribe(state => {
this.showDetail = !!state.selectedWorkbasket || state.action === 1;
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -17,11 +17,10 @@
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter.filterParams.name" (keyup.enter)="search()" class="form-control input-sm"
id="display-name-filter" placeholder="Filter name">
placeholder="Filter name">
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter.filterParams.key" (keyup.enter)="search()" class="form-control input-sm"
id="display-key-filter" placeholder="Filter key">
<input type="text" [(ngModel)]="filter.filterParams.key" (keyup.enter)="search()" class="form-control input-sm" placeholder="Filter key">
</div>
<button (click)="clear(); search()" type="button" class="btn btn-default btn-sm pull-right margin-right"
data-toggle="tooltip" title="Clear">
@ -31,13 +30,13 @@
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<input type="text" [(ngModel)]="filter.filterParams.description" (keyup.enter)="search()" class="form-control input-sm"
id="display-name-description" placeholder="Filter description">
placeholder="Filter description">
</div>
</div>
<div class="row">
<div class="col-xs-8 col-xs-offset-2">
<input type="text" [(ngModel)]="filter.filterParams.owner" (keyup.enter)="search()" class="form-control input-sm"
id="display-name-owner" placeholder="Filter owner">
placeholder="Filter owner">
</div>
<button (click)="search()" type="button" class="btn btn-default btn-sm pull-right margin-right" data-toggle="tooltip"
title="Search">

View File

@ -16,6 +16,6 @@
<span class="footer pull-right" [hidden]="numberOfItems === 0">
<i [innerHTML]="getPagesTextToShow()"></i>
</span>
<span class="footer pull-right" [hidden]="numberOfItems !== 0 && page.totalElements !== 0">
<span class="footer pull-right" [hidden]="numberOfItems !== 0 && page?.totalElements !== 0">
<i>Loading...</i>
</span>

View File

@ -46,7 +46,6 @@ export class PaginationComponent implements OnChanges {
if (this.previousPageSelected !== page) {
this.changePage.emit(page);
this.previousPageSelected = page;
this.page.number = page;
this.pageSelected = page;
}
}

View File

@ -19,7 +19,7 @@
{{dataSource.selected?.name}}
</label>
</span>
<div class="input-group">
<div class="input-group form-control">
<div>{{dataSource.selected?.accessId}}</div>
</div>
</div>
@ -33,11 +33,11 @@
<input #inputTypeAhead class="form-control input-text" [ngClass]="{'invalid': !dataSource.length && isRequired}" (blur)="typeaheadOnSelect({'item':dataSource.selected})"
name="accessItem-{{index}}" [required]="isRequired ? 'required' : null" #accessItemName="ngModel" [(ngModel)]="value"
[typeahead]="dataSource" typeaheadOptionField="name" [typeaheadItemTemplate]="customItemTemplate"
(typeaheadOnSelect)="typeaheadOnSelect($event, index)" [typeaheadScrollable]="true"
(typeaheadOnSelect)="typeaheadOnSelect($event)" [typeaheadScrollable]="true"
[typeaheadOptionsInScrollableView]="typeaheadOptionsInScrollableView" [typeaheadMinLength]="typeaheadMinLength"
[typeaheadWaitMs]="typeaheadWaitMs" (typeaheadLoading)="changeTypeaheadLoading($event)" placeholder="{{displayError? placeHolderMessage: ''}}"
[@validation]="validationValue">
<button *ngIf="!typeaheadLoading" type="button" title="search" class="btn rounded blue">
<button *ngIf="!typeaheadLoading" type="button" title="search" class="btn rounded blue search-button" >
<span class="material-icons md-20 blue">search</span>
</button>
<div *ngIf="typeaheadLoading" class="loading">

View File

@ -5,55 +5,53 @@
height: 47px;
& label {
margin-bottom: 0px;
padding: 0px 12px;
font-style: italic;
//font-style: italic;
//color: grey;
overflow: hidden;
}
&> div{
&> div {
border-bottom: 1px solid $pallete-blue;
margin-top: 2px;
padding-left: 12px;
min-width: 175px;
max-width: 175px;
}
}
> div{
border-bottom: 1px solid $pallete-blue !important;
border-radius: 4px !important;
height: 32px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
margin-right: 35px;
padding-left: 12px;
}
}
.custom-form-control {
margin-top: -5px;
height: 40px;
max-height: 57px;
& .input-group{
width: 100%;
}
}
.input-text {
background: none;
margin-top: 4px;
margin-bottom: 16px;
background: white;
box-shadow: none;
border: none;
padding: 0px;
border-radius: 0px;
border-radius: 4px !important;
//border-color: lightgray;
border-bottom: 1px solid $pallete-blue !important;
min-width: 150px;
height: 28px;
border-bottom: 1px solid $pallete-blue;
&:focus{
border-bottom: 1px solid $aquamarine;
}
padding-left: 12px;
padding-right: 35px;
height: 32px;
padding: 13px 0 13px 12px;
}
.field-label-wrapper{
position: relative;
height: 28px;
padding-left: 12px;
box-sizing: content-box;
overflow: hidden;
pointer-events: none;
@ -63,7 +61,7 @@
}
.form-control:focus {
border-color: none;
//border-color: unset;
box-shadow: none;
}
@ -77,7 +75,11 @@
position: absolute;
top: 0px;
right: 0px;
padding-top: 0;
}
.search-button {
z-index: 999;
margin-top: 8px;
}
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
@ -97,5 +99,5 @@
}
.invalid {
border-bottom: 1px solid $invalid;
color: $invalid;
}

View File

@ -1,5 +1,6 @@
export enum ACTION {
DEFAULT,
CREATE,
COPY
COPY,
READ
}

View File

@ -0,0 +1,10 @@
// Remnant from old design, needs to be removed, type reference should instead => models/Links.ts
export class LinksWorkbasketSummary {
constructor(
self?,
distributionTargets?,
accessItems?,
public allWorkbaskets?: { 'href': string }
) {
}
}

View File

@ -229,7 +229,7 @@ export const notifications = new Map<NOTIFICATION_TYPES, Pair>([
// taskdetails.component
[NOTIFICATION_TYPES.INFO_ALERT, new Pair(
'',
'Refreshed selected classification'
'Information restored'
)],
// classification-details.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_4, new Pair(

View File

@ -0,0 +1,7 @@
import { Links } from './links';
import { WorkbasketAccessItems } from './workbasket-access-items';
export interface WorkbasketAccessItemsRepresentation {
accessItems: WorkbasketAccessItems[];
_links: Links;
}

View File

@ -1,9 +0,0 @@
import { Links } from './links';
import { WorkbasketAccessItems } from './workbasket-access-items';
export class WorkbasketAccessItemsResource {
constructor(
public accessItems: Array<WorkbasketAccessItems> = [],
public _links: Links = {}
) { }
}

View File

@ -1,30 +1,29 @@
import { Links } from './links';
export class WorkbasketAccessItems {
constructor(
public accessItemId: string = '',
public workbasketId: string = '',
public accessId: string = '',
public accessName: string = '',
public permRead: boolean = false,
public permOpen: boolean = false,
public permAppend: boolean = false,
public permTransfer: boolean = false,
public permDistribute: boolean = false,
public permCustom1: boolean = false,
public permCustom2: boolean = false,
public permCustom3: boolean = false,
public permCustom4: boolean = false,
public permCustom5: boolean = false,
public permCustom6: boolean = false,
public permCustom7: boolean = false,
public permCustom8: boolean = false,
public permCustom9: boolean = false,
public permCustom10: boolean = false,
public permCustom11: boolean = false,
public permCustom12: boolean = false,
public _links: Links = {}
) { }
export interface WorkbasketAccessItems {
accessItemId: string;
workbasketId: string;
workbasketKey: string;
accessId: string;
accessName: string;
permRead: boolean;
permOpen: boolean;
permAppend: boolean;
permTransfer: boolean;
permDistribute: boolean;
permCustom1: boolean;
permCustom2: boolean;
permCustom3: boolean;
permCustom4: boolean;
permCustom5: boolean;
permCustom6: boolean;
permCustom7: boolean;
permCustom8: boolean;
permCustom9: boolean;
permCustom10: boolean;
permCustom11: boolean;
permCustom12: boolean;
_links: Links;
}
export const customFieldCount: number = 12;

View File

@ -1,11 +1,8 @@
import { WorkbasketAccessItems } from './workbasket-access-items';
import { Workbasket } from './workbasket';
export class WorkbasketDefinition {
constructor(
public distributionTargets: string[],
public workbasketAccessItems: WorkbasketAccessItems[],
public workbasket: Workbasket
) {
}
export interface WorkbasketDefinition {
distributionTargets: string[];
workbasketAccessItems: WorkbasketAccessItems[];
workbasket: Workbasket;
}

View File

@ -1,10 +0,0 @@
import { WorkbasketSummary } from './workbasket-summary';
import { Links } from './links';
export class WorkbasketDistributionTargetsResource {
constructor(
public distributionTargets: Array<WorkbasketSummary> = [],
public _links: Links = null
) {
}
}

View File

@ -0,0 +1,7 @@
import { WorkbasketSummary } from './workbasket-summary';
import { Links } from './links';
export interface WorkbasketDistributionTargets {
distributionTargets: WorkbasketSummary[];
_links: Links;
}

View File

@ -0,0 +1,7 @@
import { Links } from './links';
import { Workbasket } from './workbasket';
export interface WorkbasketRepresentation {
workbaskets: Workbasket[];
_links: Links;
}

View File

@ -1,9 +0,0 @@
import { Links } from './links';
import { Workbasket } from './workbasket';
export class WorkbasketResource {
constructor(
public workbaskets: Array<Workbasket> = [],
public _links: Links = {}
) { }
}

View File

@ -0,0 +1,9 @@
import { Page } from 'app/shared/models/page';
import { WorkbasketSummary } from './workbasket-summary';
import { LinksWorkbasketSummary } from './links-workbasket-summary';
export interface WorkbasketSummaryRepresentation {
workbaskets: WorkbasketSummary[];
_links: LinksWorkbasketSummary;
page?: Page;
}

View File

@ -1,12 +0,0 @@
import { Page } from 'app/shared/models/page';
import { WorkbasketSummary } from './workbasket-summary';
import { Links } from './links';
export class WorkbasketSummaryResource {
constructor(
public workbaskets: Array<WorkbasketSummary> = [],
public _links: Links = {},
public page: Page = new Page()
) {
}
}

View File

@ -1,24 +1,20 @@
import { ICONTYPES } from './icon-types';
import { Page } from './page';
import { Links } from './links';
export class WorkbasketSummary {
constructor(
public workbasketId?: string,
public key?: string,
public name?: string,
public description?: string,
public owner?: string,
public modified?: string,
public domain?: string,
public type: string = ICONTYPES.PERSONAL,
public orgLevel1?: string,
public orgLevel2?: string,
public orgLevel3?: string,
public orgLevel4?: string,
public markedForDeletion: boolean = false,
public _links?: Links,
public page?: Page
) {
}
export interface WorkbasketSummary {
workbasketId?: string,
key?: string,
name?: string,
domain?: string,
type?: ICONTYPES,
description?: string,
owner?: string,
custom1?: string,
custom2?: string,
custom3?: string,
custom4?: string,
orgLevel1?: string,
orgLevel2?: string,
orgLevel3?: string,
orgLevel4?: string,
markedForDeletion?: boolean,
}

View File

@ -1,28 +1,26 @@
import { Links } from './links';
import { ICONTYPES } from './icon-types';
export class Workbasket {
constructor(
public workbasketId?: string,
public created?: string,
public key?: string,
public domain?: string,
public type: ICONTYPES = ICONTYPES.PERSONAL,
public modified?: string,
public name?: string,
public description?: string,
public owner?: string,
public custom1?: string,
public custom2?: string,
public custom3?: string,
public custom4?: string,
public orgLevel1?: string,
public orgLevel2?: string,
public orgLevel3?: string,
public orgLevel4?: string,
public _links: Links = {}
) {
}
export interface Workbasket {
workbasketId?: string;
key?: string;
name?: string;
domain?: string;
type?: ICONTYPES;
description?: string;
owner?: string;
custom1?: string;
custom2?: string;
custom3?: string;
custom4?: string;
orgLevel1?: string;
orgLevel2?: string;
orgLevel3?: string;
orgLevel4?: string;
markedForDeletion?: boolean;
created?: string;
modified?: string;
_links?: Links;
}
export const customFieldCount: number = 4;

View File

@ -4,22 +4,22 @@ import { HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment';
import { Workbasket } from 'app/shared/models/workbasket';
import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items';
import { WorkbasketSummaryResource } from 'app/shared/models/workbasket-summary-resource';
import { WorkbasketAccessItemsResource } from 'app/shared/models/workbasket-access-items-resource';
import { WorkbasketDistributionTargetsResource } from 'app/shared/models/workbasket-distribution-targets-resource';
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 { 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 { WorkbasketResource } from '../../models/workbasket-resource';
import { WorkbasketRepresentation } from '../../models/workbasket-representation';
@Injectable()
export class WorkbasketService {
public workBasketSelected = new Subject<string>();
public workBasketSaved = new Subject<number>();
private workbasketSummaryRef: Observable<WorkbasketSummaryResource> = new Observable();
private workbasketSummaryRef: Observable<WorkbasketSummaryRepresentation> = new Observable();
constructor(
private httpClient: HttpClient,
@ -47,7 +47,7 @@ export class WorkbasketService {
return this.domainService.getSelectedDomain()
.pipe(mergeMap(domain => {
this.workbasketSummaryRef = this.httpClient.get<WorkbasketSummaryResource>(
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))}`
@ -65,8 +65,8 @@ export class WorkbasketService {
}
// GET
getAllWorkBaskets(): Observable<WorkbasketResource> {
return this.httpClient.get<WorkbasketResource>(`${environment.taskanaRestUrl}/v1/workbaskets?required-permission=OPEN`);
getAllWorkBaskets(): Observable<WorkbasketRepresentation> {
return this.httpClient.get<WorkbasketRepresentation>(`${environment.taskanaRestUrl}/v1/workbaskets?required-permission=OPEN`);
}
// POST
@ -90,8 +90,8 @@ export class WorkbasketService {
}
// GET
getWorkBasketAccessItems(url: string): Observable<WorkbasketAccessItemsResource> {
return this.httpClient.get<WorkbasketAccessItemsResource>(url);
getWorkBasketAccessItems(url: string): Observable<WorkbasketAccessItemsRepresentation> {
return this.httpClient.get<WorkbasketAccessItemsRepresentation>(url);
}
// POST
@ -106,14 +106,14 @@ export class WorkbasketService {
}
// GET
getWorkBasketsDistributionTargets(url: string): Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.get<WorkbasketDistributionTargetsResource>(url);
getWorkBasketsDistributionTargets(url: string): Observable<WorkbasketDistributionTargets> {
return this.httpClient.get<WorkbasketDistributionTargets>(url);
}
// PUT
updateWorkBasketsDistributionTargets(url: string, distributionTargetsIds: Array<string>):
Observable<WorkbasketDistributionTargetsResource> {
return this.httpClient.put<WorkbasketDistributionTargetsResource>(url, distributionTargetsIds);
Observable<WorkbasketDistributionTargets> {
return this.httpClient.put<WorkbasketDistributionTargets>(url, distributionTargetsIds);
}
// DELETE

View File

@ -1,4 +1,5 @@
import { EngineConfigurationState } from './engine-configuration-store/engine-configuration.state';
import { ClassificationState } from './classification-store/classification.state';
import { WorkbasketState } from './workbasket-store/workbasket.state';
export const STATES = [EngineConfigurationState, ClassificationState];
export const STATES = [EngineConfigurationState, ClassificationState, WorkbasketState];

View File

@ -0,0 +1,102 @@
import { Workbasket } from '../../models/workbasket';
import { TaskanaQueryParameters } from '../../util/query-parameters';
import { Direction } from '../../models/sorting';
import { ACTION } from '../../models/action';
import { WorkbasketAccessItems } from '../../models/workbasket-access-items';
// Workbasket List
export class GetWorkbasketsSummary {
static readonly type = '[Workbasket List] Get all workbaskets\' summary';
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) {
}
}
export class SelectWorkbasket {
static readonly type = '[Workbasket] Select a workbasket';
constructor(public workbasketId: string) {
}
}
export class DeselectWorkbasket {
static readonly type = '[Workbasket] Deselect workbasket';
}
export class CreateWorkbasket {
static readonly type = '[Workbasket] Create new workbasket';
}
export class SetActiveAction {
static readonly type = '[Workbasket] Specify current action';
constructor(public action: ACTION) {
}
}
// Workbasket Information
export class SaveNewWorkbasket {
static readonly type = '[Workbasket] Save new workbasket';
constructor(public workbasket: Workbasket) {
}
}
export class CopyWorkbasket {
static readonly type = '[Workbasket] Copy selected workbasket';
constructor(public workbasket: Workbasket) {
}
}
export class UpdateWorkbasket {
static readonly type = '[Workbasket] Update a workbasket';
constructor(public url: string, public workbasket: Workbasket) {
}
}
export class RemoveDistributionTarget {
static readonly type = '[Workbasket] Remove distribution targets of selected workbasket';
constructor(public url: string) {
}
}
export class MarkWorkbasketForDeletion {
static readonly type = '[Workbasket] Mark selected workbasket for deletion';
constructor(public url: string) {
}
}
// Workbasket Access Items
export class GetWorkbasketAccessItems {
static readonly type = '[Workbasket] Get all workbasket access items';
constructor(public url: string) {
}
}
export class UpdateWorkbasketAccessItems {
static readonly type = '[Workbasket] Update selected workbasket\'s access items';
constructor(public url: string, public workbasketAccessItems: WorkbasketAccessItems[]) {
}
}
// Workbasket Distribution Targets
export class GetWorkbasketDistributionTargets {
static readonly type = '[Workbasket] Get all workbasket distribution targets';
constructor(public url: string) {
}
}
export class UpdateWorkbasketDistributionTargets {
static readonly type = '[Workbasket] Update workbasket distribution targets';
constructor(public url: string, public distributionTargetsIds: string[]) {
}
}

View File

@ -0,0 +1,56 @@
import { Selector } from '@ngxs/store';
import { WorkbasketState, WorkbasketStateModel } from './workbasket.state';
import { WorkbasketSummary } from '../../models/workbasket-summary';
import { WorkbasketSummaryRepresentation } from '../../models/workbasket-summary-representation';
import { ACTION } from '../../models/action';
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
import { WorkbasketDistributionTargets } from '../../models/workbasket-distribution-targets';
import { Workbasket } from '../../models/workbasket';
export class WorkbasketSelectors {
// Workbasket
@Selector([WorkbasketState])
static selectedWorkbasket(state: WorkbasketStateModel): Workbasket {
return { ...state.selectedWorkbasket };
}
@Selector([WorkbasketState])
static workbasketsSummary(state: WorkbasketStateModel): WorkbasketSummary[] {
return state.paginatedWorkbasketsSummary.workbaskets;
}
@Selector([WorkbasketState])
static workbasketsSummaryRepresentation(state: WorkbasketStateModel): WorkbasketSummaryRepresentation {
return state.paginatedWorkbasketsSummary;
}
@Selector([WorkbasketState])
static workbasketActiveAction(state: WorkbasketStateModel): ACTION {
return state.action;
}
@Selector([WorkbasketState])
static selectedWorkbasketAndAction(state: WorkbasketStateModel): WorkbasketAndAction {
return {
selectedWorkbasket: state.selectedWorkbasket,
action: state.action
};
}
// Workbasket Access Items
@Selector([WorkbasketState])
static workbasketAccessItems(state: WorkbasketStateModel): WorkbasketAccessItemsRepresentation {
return state.workbasketAccessItems;
}
// Workbasket Distribution Targets
@Selector([WorkbasketState])
static workbasketDistributionTargets(state: WorkbasketStateModel): WorkbasketDistributionTargets {
return state.workbasketDistributionTargets;
}
}
export interface WorkbasketAndAction {
selectedWorkbasket: Workbasket,
action: ACTION
}

View File

@ -0,0 +1,261 @@
import { Action, NgxsAfterBootstrap, State, StateContext } from '@ngxs/store';
import { take, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { Location } from '@angular/common';
import { WorkbasketService } from '../../services/workbasket/workbasket.service';
import { Workbasket } from '../../models/workbasket';
import { CopyWorkbasket,
CreateWorkbasket,
DeselectWorkbasket,
GetWorkbasketAccessItems,
GetWorkbasketDistributionTargets,
GetWorkbasketsSummary,
MarkWorkbasketForDeletion,
RemoveDistributionTarget,
SaveNewWorkbasket,
SelectWorkbasket,
SetActiveAction,
UpdateWorkbasket, UpdateWorkbasketAccessItems,
UpdateWorkbasketDistributionTargets } from './workbasket.actions';
import { WorkbasketSummaryRepresentation } from '../../models/workbasket-summary-representation';
import { ACTION } from '../../models/action';
import { DomainService } from '../../services/domain/domain.service';
import { NOTIFICATION_TYPES } from '../../models/notifications';
import { NotificationService } from '../../services/notifications/notification.service';
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
import { WorkbasketDistributionTargets } from '../../models/workbasket-distribution-targets';
import { WorkbasketSummary } from '../../models/workbasket-summary';
class InitializeStore {
static readonly type = '[Workbasket] Initializing state';
}
@State<WorkbasketStateModel>({ name: 'workbasket' })
export class WorkbasketState implements NgxsAfterBootstrap {
constructor(
private workbasketService: WorkbasketService,
private domainService: DomainService,
private location: Location,
private notificationService: NotificationService
) {
}
@Action(GetWorkbasketsSummary)
getWorkbasketsSummary(ctx: StateContext<WorkbasketStateModel>, action: GetWorkbasketsSummary): Observable<any> {
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).pipe(
take(1), tap(paginatedWorkbasketsSummary => {
ctx.patchState(
{ paginatedWorkbasketsSummary }
);
})
);
}
@Action(SelectWorkbasket)
selectWorkbasket(ctx: StateContext<WorkbasketStateModel>, action: SelectWorkbasket): Observable<any> {
this.location.go(this.location.path().replace(/(workbaskets).*/g, `workbaskets/(detail:${action.workbasketId})`));
const id = action.workbasketId;
if (typeof id !== 'undefined') {
return this.workbasketService.getWorkBasket(id).pipe(take(1), tap(
selectedWorkbasket => {
ctx.patchState({
selectedWorkbasket,
action: ACTION.READ
});
}
));
}
return of(null);
}
@Action(DeselectWorkbasket)
deselectWorkbasket(ctx: StateContext<WorkbasketStateModel>): Observable<any> {
this.location.go(this.location.path().replace(/(workbaskets).*/g, 'workbaskets'));
ctx.patchState({
selectedWorkbasket: undefined,
action: ACTION.READ
});
return of(null);
}
@Action(CreateWorkbasket)
createWorkbasket(ctx: StateContext<WorkbasketStateModel>): Observable<any> {
this.location.go(this.location.path().replace(/(workbaskets).*/g, 'workbaskets/(detail:new-workbasket)'));
ctx.patchState({
selectedWorkbasket: undefined,
action: ACTION.CREATE
});
return of(null);
}
@Action(SetActiveAction)
setActiveAction(ctx: StateContext<WorkbasketStateModel>, action: SetActiveAction): Observable<any> {
ctx.patchState({ action: action.action });
return of(null);
}
@Action(SaveNewWorkbasket)
saveNewWorkbasket(ctx: StateContext<WorkbasketStateModel>, action: SaveNewWorkbasket): Observable<any> {
return this.workbasketService.createWorkbasket(action.workbasket).pipe(take(1), tap(
workbasketUpdated => {
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_11,
new Map<string, string>([['workbasketKey', workbasketUpdated.key]])
);
this.selectWorkbasket(ctx, workbasketUpdated.workbasketId);
this.location.go(this.location.path().replace(/(workbaskets).*/g, 'workbaskets'));
}, error => {
this.notificationService.triggerError(NOTIFICATION_TYPES.CREATE_ERR_2, error);
}
));
}
@Action(CopyWorkbasket)
copyWorkbasket(ctx: StateContext<WorkbasketStateModel>, action: CopyWorkbasket): Observable<any> {
this.location.go(this.location.path().replace(/(workbaskets).*/g, 'workbaskets/(detail:new-workbasket)'));
ctx.patchState({
action: ACTION.COPY
});
return of(null);
}
@Action(UpdateWorkbasket)
updateWorkbasket(ctx: StateContext<WorkbasketStateModel>, action: UpdateWorkbasket): Observable<any> {
return this.workbasketService.updateWorkbasket(action.url, action.workbasket).pipe(take(1), tap(
updatedWorkbasket => {
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_10,
new Map<string, string>([['workbasketKey', updatedWorkbasket.key]])
);
const paginatedWorkbasketSummary = { ...ctx.getState().paginatedWorkbasketsSummary };
paginatedWorkbasketSummary.workbaskets = updateWorkbasketSummaryRepresentation(
paginatedWorkbasketSummary.workbaskets, action.workbasket
);
ctx.patchState({
selectedWorkbasket: updatedWorkbasket,
paginatedWorkbasketsSummary: paginatedWorkbasketSummary
});
}, error => {
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_4, error);
}
));
}
@Action(RemoveDistributionTarget)
removeDistributionTarget(ctx: StateContext<WorkbasketStateModel>, action: RemoveDistributionTarget): Observable<any> {
return this.workbasketService.removeDistributionTarget(action.url).pipe(take(1), tap(
() => {
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_9,
new Map<string, string>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]])
);
}, error => {
this.notificationService.triggerError(NOTIFICATION_TYPES.REMOVE_ERR_2,
error,
new Map<String, String>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]]));
}
));
}
@Action(MarkWorkbasketForDeletion)
deleteWorkbasket(ctx: StateContext<WorkbasketStateModel>, action: MarkWorkbasketForDeletion): Observable<any> {
return this.workbasketService.markWorkbasketForDeletion(action.url).pipe(take(1), tap(
response => {
if (response.status === 202) {
this.notificationService.triggerError(NOTIFICATION_TYPES.MARK_ERR,
undefined,
new Map<String, String>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]]));
} else {
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_12,
new Map<string, string>([['workbasketId', ctx.getState().selectedWorkbasket.workbasketId]])
);
}
}
));
}
@Action(GetWorkbasketAccessItems)
getWorkbasketAccessItems(ctx: StateContext<WorkbasketStateModel>, action: GetWorkbasketAccessItems): Observable<any> {
return this.workbasketService.getWorkBasketAccessItems(action.url).pipe(take(1), tap(
workbasketAccessItemsRepresentation => {
ctx.patchState({
workbasketAccessItems: workbasketAccessItemsRepresentation
});
}
));
}
@Action(UpdateWorkbasketAccessItems)
updateWorkbasketAccessItems(ctx: StateContext<WorkbasketStateModel>,
action: UpdateWorkbasketAccessItems): Observable<any> {
return this.workbasketService.updateWorkBasketAccessItem(action.url, action.workbasketAccessItems)
.pipe(take(1), tap(workbasketAccessItems => {
ctx.patchState({
workbasketAccessItems
});
this.notificationService.showToast(NOTIFICATION_TYPES.SUCCESS_ALERT_7,
new Map<string, string>([['workbasketKey', ctx.getState().selectedWorkbasket.key]]));
}, error => {
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_2, error);
}));
}
@Action(GetWorkbasketDistributionTargets)
getWorkbasketDistributionTargets(ctx: StateContext<WorkbasketStateModel>, action: GetWorkbasketDistributionTargets): Observable<any> {
return this.workbasketService.getWorkBasketsDistributionTargets(action.url).pipe(take(1), tap(
workbasketDistributionTargets => {
ctx.patchState({
workbasketDistributionTargets
});
}
));
}
@Action(UpdateWorkbasketDistributionTargets)
updateWorkbasketDistributionTargets(
ctx: StateContext<WorkbasketStateModel>,
action: UpdateWorkbasketDistributionTargets
): Observable<any> {
return this.workbasketService.updateWorkBasketsDistributionTargets(action.url, action.distributionTargetsIds).pipe(take(1), tap(
updatedWorkbasketsDistributionTargets => {
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_8,
new Map<string, string>([['workbasketName', ctx.getState().selectedWorkbasket.name]])
);
}, error => {
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR_3, error);
}
));
}
ngxsAfterBootstrap(ctx?: StateContext<any>): void {
ctx.dispatch(new InitializeStore());
}
}
function updateWorkbasketSummaryRepresentation(
workbasketsSummary: WorkbasketSummary[],
selectedWorkbasket: Workbasket
) {
return workbasketsSummary.map(w => {
if (w.workbasketId === selectedWorkbasket.workbasketId) {
const workbasketSummary: WorkbasketSummary = selectedWorkbasket;
return workbasketSummary;
}
return w;
});
}
export interface WorkbasketStateModel {
paginatedWorkbasketsSummary: WorkbasketSummaryRepresentation,
selectedWorkbasket: Workbasket,
action: ACTION,
workbasketAccessItems: WorkbasketAccessItemsRepresentation,
workbasketDistributionTargets: WorkbasketDistributionTargets
}