Merge pull request #7137 from opf/refactor/wp-card-performance

Boards: Improve saving performance by not reloading list

[ci skip]
pull/7144/head
Oliver Günther 6 years ago committed by GitHub
commit 45c4425ef4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      frontend/src/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts
  2. 2
      frontend/src/app/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts
  3. 4
      frontend/src/app/components/op-context-menu/handlers/op-types-context-menu.directive.ts
  4. 2
      frontend/src/app/components/op-context-menu/handlers/wp-status-dropdown-menu.directive.ts
  5. 2
      frontend/src/app/components/states.service.ts
  6. 4
      frontend/src/app/components/work-packages/work-package-authorization.service.ts
  7. 4
      frontend/src/app/components/work-packages/work-package-cache.service.spec.ts
  8. 8
      frontend/src/app/components/work-packages/work-package-cache.service.ts
  9. 2
      frontend/src/app/components/work-packages/work-package.service.ts
  10. 6
      frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts
  11. 6
      frontend/src/app/components/work-packages/wp-watcher-button/wp-watcher-button.component.ts
  12. 4
      frontend/src/app/components/wp-activity/activity-link.component.ts
  13. 2
      frontend/src/app/components/wp-activity/revision/revision-activity.component.ts
  14. 2
      frontend/src/app/components/wp-activity/user/user-activity.component.ts
  15. 4
      frontend/src/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts
  16. 2
      frontend/src/app/components/wp-by-version-graph/wp-by-version-graph.component.ts
  17. 13
      frontend/src/app/components/wp-card-view/card-reorder-query.service.ts
  18. 2
      frontend/src/app/components/wp-card-view/wp-card-view.component.html
  19. 1
      frontend/src/app/components/wp-card-view/wp-card-view.component.sass
  20. 181
      frontend/src/app/components/wp-card-view/wp-card-view.component.ts
  21. 2
      frontend/src/app/components/wp-copy/wp-copy.controller.ts
  22. 2
      frontend/src/app/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts
  23. 6
      frontend/src/app/components/wp-edit-form/work-package-changeset.ts
  24. 5
      frontend/src/app/components/wp-edit-form/work-package-edit-form.ts
  25. 2
      frontend/src/app/components/wp-edit-form/work-package-editing-service.ts
  26. 8
      frontend/src/app/components/wp-edit/wp-edit-field/wp-edit-field-group.directive.ts
  27. 2
      frontend/src/app/components/wp-edit/wp-notification.service.ts
  28. 2
      frontend/src/app/components/wp-fast-table/builders/highlighting/row-highlight-render-pass.ts
  29. 38
      frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts
  30. 8
      frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/single-hierarchy-row-builder.ts
  31. 4
      frontend/src/app/components/wp-fast-table/builders/relation-cell-builder.ts
  32. 8
      frontend/src/app/components/wp-fast-table/builders/relations/relation-row-builder.ts
  33. 2
      frontend/src/app/components/wp-fast-table/builders/relations/relations-render-pass.ts
  34. 8
      frontend/src/app/components/wp-fast-table/builders/rows/single-row-builder.ts
  35. 26
      frontend/src/app/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts
  36. 2
      frontend/src/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts
  37. 2
      frontend/src/app/components/wp-fast-table/state/wp-table-additional-elements.service.ts
  38. 2
      frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts
  39. 4
      frontend/src/app/components/wp-fast-table/wp-fast-table.ts
  40. 2
      frontend/src/app/components/wp-fast-table/wp-table-editing.ts
  41. 4
      frontend/src/app/components/wp-inline-create/inline-create-row-builder.ts
  42. 4
      frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts
  43. 12
      frontend/src/app/components/wp-list/wp-list-checksum.service.ts
  44. 22
      frontend/src/app/components/wp-list/wp-list.service.ts
  45. 8
      frontend/src/app/components/wp-list/wp-states-initialization.service.ts
  46. 4
      frontend/src/app/components/wp-query/url-params-helper.ts
  47. 2
      frontend/src/app/components/wp-relations/embedded/children/wp-children-query.component.ts
  48. 4
      frontend/src/app/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts
  49. 2
      frontend/src/app/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts
  50. 6
      frontend/src/app/components/wp-relations/embedded/relations/wp-relation-query.component.ts
  51. 2
      frontend/src/app/components/wp-relations/embedded/wp-relation-query.base.ts
  52. 4
      frontend/src/app/components/wp-relations/wp-relation-row/wp-relation-row.component.ts
  53. 2
      frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts
  54. 4
      frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-create.component.ts
  55. 4
      frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts
  56. 8
      frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts
  57. 8
      frontend/src/app/components/wp-relations/wp-relations.component.ts
  58. 16
      frontend/src/app/components/wp-relations/wp-relations.service.ts
  59. 2
      frontend/src/app/components/wp-single-view-tabs/activity-panel/activity-on-overview.component.ts
  60. 2
      frontend/src/app/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts
  61. 4
      frontend/src/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts
  62. 6
      frontend/src/app/components/wp-single-view-tabs/wp-linked-resource-cache.service.ts
  63. 4
      frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts
  64. 2
      frontend/src/app/components/wp-table/table-actions/actions/details-table-action.ts
  65. 2
      frontend/src/app/components/wp-table/table-actions/actions/unlink-table-action.ts
  66. 6
      frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts
  67. 8
      frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts
  68. 9
      frontend/src/app/components/wp-table/wp-table-refresh-request.service.ts
  69. 2
      frontend/src/app/modules/boards/board/board-actions/status-action.service.ts
  70. 2
      frontend/src/app/modules/boards/board/board-cache.service.ts
  71. 4
      frontend/src/app/modules/boards/board/board-list/board-list.component.html
  72. 30
      frontend/src/app/modules/boards/board/board-list/board-list.component.ts
  73. 2
      frontend/src/app/modules/boards/board/board.service.ts
  74. 2
      frontend/src/app/modules/boards/board/inline-add/board-inline-add-autocompleter.component.ts
  75. 2
      frontend/src/app/modules/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts
  76. 11
      frontend/src/app/modules/boards/drag-and-drop/drag-and-drop.service.ts
  77. 33
      frontend/src/app/modules/boards/drag-and-drop/reorder-query.service.ts
  78. 2
      frontend/src/app/modules/boards/index-page/boards-index-page.component.ts
  79. 2
      frontend/src/app/modules/boards/openproject-boards.module.ts
  80. 2
      frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts
  81. 2
      frontend/src/app/modules/fields/display/field-types/wp-display-highlighted-resource-field.module.ts
  82. 2
      frontend/src/app/modules/fields/edit/edit-field.component.ts
  83. 2
      frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts
  84. 2
      frontend/src/app/modules/global_search/global-search-input.component.ts
  85. 8
      frontend/src/app/modules/grids/widgets/time-entries-current-user/time-entries-current-user.component.ts
  86. 17
      frontend/src/app/modules/hal/dm-services/query-dm.service.ts
  87. 2
      frontend/src/app/modules/hal/dm-services/query-form-dm.service.ts
  88. 1
      frontend/src/app/modules/hal/resources/custom-action-resource.ts
  89. 33
      frontend/src/app/modules/hal/resources/hal-resource.ts
  90. 1
      frontend/src/app/modules/hal/resources/query-resource.ts
  91. 3
      frontend/src/app/modules/hal/resources/relation-resource.ts
  92. 1
      frontend/src/app/modules/hal/resources/user-resource.ts
  93. 10
      frontend/src/app/modules/hal/resources/work-package-resource.ts
  94. 2
      frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts
  95. 5
      frontend/src/app/modules/work_packages/routing/wp-list/wp-list.component.ts
  96. 1
      frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts

@ -132,7 +132,7 @@ export class WpDestroyModal extends OpModalComponent implements OnInit {
}
this.busy = true;
this.WorkPackageService.performBulkDelete(this.workPackages.map(el => el.id), true)
this.WorkPackageService.performBulkDelete(this.workPackages.map(el => el.id!), true)
.then(() => {
this.busy = false;
this.closeMe($event);

@ -194,7 +194,7 @@ export class OpSettingsMenuDirective extends OpContextMenuTrigger implements OnD
icon: 'icon-save',
onClick: ($event:JQueryEventObject) => {
const query = this.query;
if (!query.id && this.allowQueryAction($event, 'updateImmediately')) {
if (!query.persisted && this.allowQueryAction($event, 'updateImmediately')) {
this.opModalService.show(SaveQueryModal, this.injector);
} else if (query.id && this.allowQueryAction($event, 'updateImmediately')) {
this.wpListService.save(query);

@ -90,9 +90,9 @@ export class OpTypesContextMenuDirective extends OpContextMenuTrigger {
return {
disabled: false,
linkText: type.name,
href: this.$state.href(this.stateName, { type: type.id }),
href: this.$state.href(this.stateName, { type: type.id! }),
ariaLabel: type.name,
class: Highlighting.dotClass('type', type.id),
class: Highlighting.dotClass('type', type.id!),
onClick: ($event:JQueryEventObject) => {
if (LinkHandling.isClickedWithModifier($event)) {
return false;

@ -93,7 +93,7 @@ export class WorkPackageStatusDropdownDirective extends OpContextMenuTrigger {
linkText: status.name,
postIcon: status.isReadonly ? 'icon-locked' : null,
postIconTitle: this.I18n.t('js.work_packages.message_work_package_read_only'),
class: Highlighting.dotClass('status', status.getId()),
class: Highlighting.dotClass('status', status.id!),
onClick: () => {
this.updateStatus(status);
return true;

@ -57,7 +57,7 @@ export class States extends StatesGroup {
state = this.additional[stateName] = multiInput<HalResource>();
}
return state && state.get(resource.id);
return state && state.get(resource.id!);
}
public add(name:string, state:MultiInputState<HalResource>) {

@ -51,9 +51,9 @@ export class WorkPackageAuthorization {
public copyLink() {
const stateName = this.$state.current.name as string;
if (stateName.indexOf('work-packages.list.details') === 0) {
return this.PathHelper.workPackageDetailsCopyPath(this.project.identifier, this.workPackage.id);
return this.PathHelper.workPackageDetailsCopyPath(this.project.identifier, this.workPackage.id!);
} else {
return this.PathHelper.workPackageCopyPath(this.workPackage.id);
return this.PathHelper.workPackageCopyPath(this.workPackage.id!);
}
}

@ -105,7 +105,7 @@ describe('WorkPackageCacheService', () => {
take(1)
)
.subscribe((wp:WorkPackageResource) => {
expect(wp.id).toEqual('1');
expect(wp.id!).toEqual('1');
done();
});
@ -120,7 +120,7 @@ describe('WorkPackageCacheService', () => {
takeWhile((wp) => count < 2)
)
.subscribe((wp:WorkPackageResource) => {
expect(wp.id).toEqual('1');
expect(wp.id!).toEqual('1');
count += 1;
if (count === 2) {

@ -37,8 +37,8 @@ import {Injectable} from '@angular/core';
import {debugLog} from "core-app/helpers/debug_output";
import {WorkPackageDmService} from "core-app/modules/hal/dm-services/work-package-dm.service";
function getWorkPackageId(id:number | string):string {
return (id || '__new_work_package__').toString();
function getWorkPackageId(id:string|null):string {
return (id || 'new').toString();
}
@Injectable()
@ -57,7 +57,7 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
updateWorkPackage(wp:WorkPackageResource, immediate:boolean = false) {
if (immediate) {
const wpId = getWorkPackageId(wp.id);
const wpId = getWorkPackageId(wp.id!);
this.multiState.get(wpId).putValue(wp);
return;
}
@ -68,7 +68,7 @@ export class WorkPackageCacheService extends StateCacheService<WorkPackageResour
updateWorkPackageList(list:WorkPackageResource[], skipOnIdentical = true) {
for (var i of list) {
const wp = i;
const workPackageId = getWorkPackageId(wp.id);
const workPackageId = getWorkPackageId(wp.id!);
const state = this.multiState.get(workPackageId);
// If the work package is new, ignore the schema

@ -63,7 +63,7 @@ export class WorkPackageService {
promise
.then(() => {
this.NotificationsService.addSuccess(this.text.successful_delete);
this.wpTableRefresh.request('Bulk delete removed elements', 'delete', { visible: true });
this.wpTableRefresh.request('Bulk delete removed elements', { visible: true });
if (this.$state.includes('**.list.details.**')
&& ids.indexOf(this.$state.params.workPackageId) > -1) {

@ -148,7 +148,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
.pipe(
takeUntil(componentDestroyed(this)),
distinctUntilChanged<ResourceContextChange>((a, b) => _.isEqual(a, b)),
map(() => this.wpEditing.temporaryEditResource(this.workPackage.id).value!)
map(() => this.wpEditing.temporaryEditResource(this.workPackage.id!).value!)
)
.subscribe((resource:WorkPackageResource) => {
// Prepare the fields that are required always
@ -158,7 +158,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
this.projectContext = {matches: false, href: null};
} else {
this.projectContext = {
href: this.PathHelper.projectWorkPackagePath(resource.project.idFromLink, this.workPackage.id),
href: this.PathHelper.projectWorkPackagePath(resource.project.idFromLink, this.workPackage.id!),
matches: resource.project.href === this.currentProject.apiv3Path
};
}
@ -173,7 +173,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy {
// Update the resource context on every update to the temporary resource.
// This allows detecting a changed type value in a new work package.
this.wpEditing.temporaryEditResource(this.workPackage.id)
this.wpEditing.temporaryEditResource(this.workPackage.id!)
.values$()
.pipe(
takeUntil(componentDestroyed(this))

@ -55,7 +55,7 @@ export class WorkPackageWatcherButtonComponent implements OnInit, OnDestroy {
}
ngOnInit() {
this.wpCacheService.loadWorkPackage(this.workPackage.id)
this.wpCacheService.loadWorkPackage(this.workPackage.id!)
.values$()
.pipe(
takeUntil(componentDestroyed(this))
@ -82,8 +82,8 @@ export class WorkPackageWatcherButtonComponent implements OnInit, OnDestroy {
const toggleLink = this.nextStateLink();
toggleLink(toggleLink.$link.payload).then(() => {
this.wpWatchersService.clear(this.workPackage.id);
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
this.wpWatchersService.clear(this.workPackage.id!);
this.wpCacheService.loadWorkPackage(this.workPackage.id!, true);
});
}

@ -7,7 +7,7 @@ import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-r
<a id ="{{ activityHtmlId }}-link"
[textContent]="activityLabel"
uiSref="work-packages.show.activity"
[uiParams]="{workPackageId: workPackage.id, '#': activityHtmlId }">
[uiParams]="{workPackageId: workPackage.id!, '#': activityHtmlId }">
</a>
`
})
@ -32,7 +32,7 @@ function activityLink() {
scope: {
},
link: function(scope:any) {
scope.workPackageId = scope.workPackage.id;
scope.workPackageId = scope.workPackage.id!;
scope.activityHtmlId = 'activity-' + scope.activityNo;
}
};

@ -94,7 +94,7 @@ export class RevisionActivityComponent implements OnInit {
this.userCacheService
.require(this.activity.author.idFromLink)
.then((user:UserResource) => {
this.userId = user.id;
this.userId = user.id!;
this.userName = user.name;
this.userActive = user.isActive;
this.userAvatar = user.avatar;

@ -112,7 +112,7 @@ export class UserActivityComponent extends WorkPackageCommentFieldHandler implem
this.userCacheService
.require(this.activity.user.idFromLink)
.then((user:UserResource) => {
this.userId = user.id;
this.userId = user.id!;
this.userName = user.name;
this.userActive = user.isActive;
this.userAvatar = user.avatar;

@ -55,7 +55,7 @@ export class WorkPackageStatusButtonComponent implements OnInit, OnDestroy {
ngOnInit() {
this.wpCacheService
.observe(this.workPackage.id)
.observe(this.workPackage.id!)
.pipe(
untilComponentDestroyed(this)
)
@ -83,7 +83,7 @@ export class WorkPackageStatusButtonComponent implements OnInit, OnDestroy {
}
public get statusHighlightClass() {
return Highlighting.inlineClass('status', this.status.getId());
return Highlighting.inlineClass('status', this.status.id!);
}
public get status():HalResource {

@ -52,7 +52,7 @@ export class WorkPackageByVersionGraphComponent implements OnInit {
}
if (this.currentGraph) {
this.wpTableRefresh.request('Refresh graph', 'update');
this.wpTableRefresh.request('Refresh graph');
}
}

@ -1,13 +0,0 @@
import {Injectable} from "@angular/core";
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {ReorderQueryService} from "core-app/modules/boards/drag-and-drop/reorder-query.service";
@Injectable()
export class CardReorderQueryService extends ReorderQueryService {
protected getCurrentOrder(querySpace:IsolatedQuerySpace):string[] {
return querySpace
.results
.mapOr((results) => results.elements.map(el => el.id.toString()), []);
}
}

@ -13,7 +13,7 @@
[attr.data-is-new]="wp.isNew || undefined"
[attr.data-work-package-id]="wp.id"
(dblclick)="handleDblClick(wp)"
[ngClass]="{'-draggable': wp.isDraggable}">
[ngClass]="{'-draggable': dragAndDropEnabled }">
<div class="wp-card--highlighting"
[ngClass]="cardHighlightingClass(wp)">

@ -11,6 +11,7 @@
.wp-card
user-select: none
width: 100%
border: 1px solid var(--widget-box-block-border-color)
border-radius: 5px

@ -4,7 +4,8 @@ import {
Component,
ElementRef,
Inject,
Injector, Input,
Injector,
Input,
OnInit,
ViewChild
} from "@angular/core";
@ -13,49 +14,45 @@ import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/iso
import {componentDestroyed, untilComponentDestroyed} from "ng2-rx-componentdestroyed";
import {QueryColumn} from "app/components/wp-query/query-column";
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource";
import {WorkPackageEmbeddedTableComponent} from "core-components/wp-table/embedded/wp-embedded-table.component";
import {I18nService} from "core-app/modules/common/i18n/i18n.service";
import {CurrentProjectService} from "core-components/projects/current-project.service";
import {WorkPackageInlineCreateService} from "core-components/wp-inline-create/wp-inline-create.service";
import {
WorkPackageTableRefreshRequest,
WorkPackageTableRefreshService
} from "core-components/wp-table/wp-table-refresh-request.service";
import {IWorkPackageCreateServiceToken} from "core-components/wp-new/wp-create.service.interface";
import {WorkPackageCreateService} from "core-components/wp-new/wp-create.service";
import {DragAndDropService} from "core-app/modules/boards/drag-and-drop/drag-and-drop.service";
import {CardReorderQueryService} from "core-components/wp-card-view/card-reorder-query.service";
import {ReorderQueryService} from "core-app/modules/boards/drag-and-drop/reorder-query.service";
import {AngularTrackingHelpers} from "core-components/angular/tracking-functions";
import {DragAndDropHelpers} from "core-app/modules/boards/drag-and-drop/drag-and-drop.helpers";
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service";
import {Highlighting} from "core-components/wp-fast-table/builders/highlighting/highlighting.functions";
import {Subject} from "rxjs";
import {WorkPackageChangeset} from "core-components/wp-edit-form/work-package-changeset";
import {CardHighlightingMode} from "core-components/wp-fast-table/builders/highlighting/highlighting-mode.const";
import {AuthorisationService} from "core-app/modules/common/model-auth/model-auth.service";
import {StateService} from "@uirouter/core";
import {States} from "core-components/states.service";
import {input} from "reactivestates";
import {switchMap, tap} from "rxjs/operators";
@Component({
selector: 'wp-card-view',
styleUrls: ['./wp-card-view.component.sass'],
templateUrl: './wp-card-view.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{ provide: ReorderQueryService, useClass: CardReorderQueryService },
]
changeDetection: ChangeDetectionStrategy.OnPush
})
export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableComponent implements OnInit {
export class WorkPackageCardViewComponent implements OnInit {
@Input() public dragAndDropEnabled:boolean;
@Input() public highlightingMode:CardHighlightingMode;
public trackByHref = AngularTrackingHelpers.trackByHref;
public query:QueryResource;
public workPackages:any[];
private _workPackages:WorkPackageResource[];
public columns:QueryColumn[];
public text = {
removeCard: this.I18n.t('js.card.remove_from_list'),
addNewCard: this.I18n.t('js.card.add_new'),
wpAddedBy: (wp:WorkPackageResource) =>
this.I18n.t('js.label_wp_id_added_by', {id: wp.id, author: wp.author.name})
this.I18n.t('js.label_wp_id_added_by', {id: wp.id!, author: wp.author.name})
};
/** Inline create / reference properties */
@ -73,12 +70,27 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
/** Whether cards are removable */
@Input() public cardsRemovable:boolean = false;
/** Container reference */
@ViewChild('container') public container:ElementRef;
/** Whether the card view has an active inline created wp */
public activeInlineCreateWp?:WorkPackageResource;
// We remember when we want to update the query with a given order
private queryUpdateRequests = input<string[]>();
// This mapped observable requests the latest query automatically.
private queryLoading = this.queryUpdateRequests
.values$()
.pipe(
// Stream the query request, switchMap will call previous requests to be cancelled
switchMap((order:string[]) => this.reorderService.saveOrderInQuery(this.query, order))
);
constructor(readonly querySpace:IsolatedQuerySpace,
readonly states:States,
readonly injector:Injector,
readonly $state:StateService,
readonly I18n:I18nService,
readonly currentProject:CurrentProjectService,
@Inject(IWorkPackageCreateServiceToken) readonly wpCreate:WorkPackageCreateService,
@ -86,19 +98,25 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
readonly wpNotifications:WorkPackageNotificationService,
readonly dragService:DragAndDropService,
readonly reorderService:ReorderQueryService,
readonly wpTableRefresh:WorkPackageTableRefreshService,
readonly authorisationService:AuthorisationService,
readonly cdRef:ChangeDetectorRef) {
super(injector);
}
ngOnInit() {
super.ngOnInit();
this.registerDragAndDrop();
this.registerCreationCallback();
// Keep query loading requests
this.queryLoading
.pipe(
untilComponentDestroyed(this)
)
.subscribe(
undefined,
(error:any) => this.wpNotifications.handleRawError(error)
);
// Update permission on model updates
this.authorisationService
.observeUntil(componentDestroyed(this))
@ -108,18 +126,13 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
this.cdRef.detectChanges();
});
this.querySpace.results
this.querySpace.query
.values$()
.pipe(
untilComponentDestroyed(this)
).subscribe((results) => {
if (this.activeInlineCreateWp) {
this.workPackages = [this.activeInlineCreateWp, ...results.$embedded.elements];
} else {
this.workPackages = results.$embedded.elements;
}
this.removeDragged();
).subscribe((query:QueryResource) => {
this.query = query;
this.workPackages = query.results.elements;
this.cdRef.detectChanges();
});
}
@ -128,20 +141,12 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
this.dragService.remove(this.container.nativeElement);
}
protected filterRefreshRequest(request:WorkPackageTableRefreshRequest):boolean {
return request.origin !== 'create';
}
public hasAssignee(wp:WorkPackageResource) {
return !!wp.assignee;
}
public get isDraggable() {
return this.configuration.dragAndDropEnabled;
}
public handleDblClick(wp:WorkPackageResource) {
this.goToWpFullView(wp.id);
this.goToWpFullView(wp.id!);
}
private goToWpFullView(wpId:string) {
@ -169,69 +174,98 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
private cardHighlighting(wp:WorkPackageResource) {
if (['status', 'priority', 'type'].includes(this.highlightingMode)) {
return Highlighting.rowClass(this.highlightingMode, wp[this.highlightingMode].getId());
return Highlighting.rowClass(this.highlightingMode, wp[this.highlightingMode].id);
}
return '';
}
private attributeDotHighlighting(type:string, wp:WorkPackageResource) {
if (this.highlightingMode === 'inline') {
return Highlighting.dotClass(type, wp.type.getId());
return Highlighting.dotClass(type, wp.type.id!);
}
return '';
}
removeDragged() {
this.container.nativeElement
.querySelectorAll('.__was_dragged')
.forEach((el:HTMLElement) => {
el.parentElement && el.parentElement!.removeChild(el);
});
}
registerDragAndDrop() {
if (!this.configuration.dragAndDropEnabled) {
return;
}
this.dragService.register({
container: this.container.nativeElement,
moves: (card:HTMLElement) => !card.dataset.isNew,
moves: (card:HTMLElement) => this.dragAndDropEnabled && !card.dataset.isNew,
onMoved: (card:HTMLElement) => {
const wpId:string = card.dataset.workPackageId!;
const toIndex = DragAndDropHelpers.findIndex(card);
this.reorderService
.move(this.querySpace, wpId, toIndex)
.then(() => this.wpTableRefresh.request('Drag and Drop moved item'));
const newOrder = this.reorderService.move(this.currentOrder, wpId, toIndex);
this.updateOrder(newOrder);
},
onRemoved: (card:HTMLElement) => {
const wpId:string = card.dataset.workPackageId!;
this.reorderService
.remove(this.querySpace, wpId)
.then(() => this.wpTableRefresh.request('Drag and Drop removed item'));
const newOrder = this.reorderService.remove(this.currentOrder, wpId);
this.updateOrder(newOrder);
},
onAdded: async (card:HTMLElement) => {
// Fix to ensure items that are virtually added get removed quickly
card.classList.add('__was_dragged');
const wpId:string = card.dataset.workPackageId!;
const toIndex = DragAndDropHelpers.findIndex(card);
const workPackage = this.states.workPackages.get(wpId).value!;
return await this.addWorkPackageToQuery(workPackage, toIndex);
const result = await this.addWorkPackageToQuery(workPackage, toIndex);
card.parentElement!.removeChild(card);
return result;
}
});
}
/**
* Get current order
*/
private get currentOrder():string[] {
return this.workPackages
.filter(wp => !wp.isNew)
.map(el => el.id!);
}
/**
* Update current order
*/
private updateOrder(newOrder:string[]) {
newOrder = _.uniq(newOrder);
this.workPackages = newOrder.map(id => this.states.workPackages.get(id).value!);
// Ensure dragged work packages are being removed.
this.queryUpdateRequests.putValue(newOrder);
this.cdRef.detectChanges();
}
/**
* Get the current work packages
*/
public get workPackages():WorkPackageResource[] {
return this._workPackages;
}
/**
* Set work packages array,
* remembering to keep the active inline-create
*/
public set workPackages(workPackages:WorkPackageResource[]) {
if (this.activeInlineCreateWp) {
this._workPackages = [this.activeInlineCreateWp, ...workPackages];
} else {
this._workPackages = [...workPackages];
}
}
/**
* Add the given work package to the query
*/
async addWorkPackageToQuery(workPackage:WorkPackageResource, toIndex:number = -1):Promise<boolean> {
try {
await this.reorderService.updateWorkPackage(this.querySpace, workPackage);
await this.reorderService.add(this.querySpace, workPackage.id, toIndex);
this.wpTableRefresh.request('Drag and Drop added item');
const newOrder = await this.reorderService.add(this.currentOrder, workPackage.id!, toIndex);
this.updateOrder(newOrder);
return true;
} catch (e) {
this.wpNotifications.handleRawError(e, workPackage);
@ -248,9 +282,8 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
this.wpCreate
.createOrContinueWorkPackage(this.currentProject.identifier)
.then((changeset:WorkPackageChangeset) => {
const wp = changeset.workPackage;
this.activeInlineCreateWp = wp;
this.workPackages = [wp, ...this.workPackages];
this.activeInlineCreateWp = changeset.workPackage;
this.workPackages = this.workPackages;
this.cdRef.detectChanges();
});
}
@ -269,12 +302,9 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
this.activeInlineCreateWp = undefined;
if (!wp.isNew) {
this.reorderService
.remove(this.querySpace, wp.id)
.then(() => this.wpTableRefresh.request('Drag and Drop removed item'));
const newOrder = this.reorderService.remove(this.currentOrder, wp.id!);
this.updateOrder(newOrder);
}
this.cdRef.detectChanges();
}
/**
@ -286,12 +316,11 @@ export class WorkPackageCardViewComponent extends WorkPackageEmbeddedTableCompon
this.activeInlineCreateWp = undefined;
// Add this item to the results
await this.reorderService.add(this.querySpace, wp.id, index);
const newOrder = await this.reorderService.add(this.currentOrder, wp.id!, index);
this.updateOrder(newOrder);
// Notify inline create service
this.wpInlineCreate.newInlineWorkPackageCreated.next(wp.id);
this.refresh();
this.wpInlineCreate.newInlineWorkPackageCreated.next(wp.id!);
}
}

@ -51,7 +51,7 @@ export class WorkPackageCopyController extends WorkPackageCreateController {
)
.subscribe((wp:WorkPackageResource) => {
if (wp.__initialized_at === this.__initialized_at) {
this.wpRelations.addCommonRelation(wp.id, 'relates', this.copiedWorkPackageId);
this.wpRelations.addCommonRelation(wp.id!, 'relates', this.copiedWorkPackageId);
}
});
}

@ -72,7 +72,7 @@ export class WpCustomActionComponent {
.then((savedWp:WorkPackageResource) => {
this.wpNotificationsService.showSave(savedWp, false);
this.workPackage = savedWp;
this.wpActivity.clear(this.workPackage.id);
this.wpActivity.clear(this.workPackage.id!);
this.wpCacheService.updateWorkPackage(savedWp);
}).catch((errorResource:any) => {
this.wpNotificationsService.handleRawError(errorResource, this.workPackage);

@ -190,7 +190,7 @@ export class WorkPackageChangeset {
this.schemaCacheService.ensureLoaded(savedWp).then(() => {
// Clear any previous activities
this.wpActivity.clear(this.workPackage.id);
this.wpActivity.clear(this.workPackage.id!);
// Initialize any potentially new HAL values
savedWp.retainFrom(this.workPackage);
@ -351,7 +351,7 @@ export class WorkPackageChangeset {
private buildResource() {
if (this.empty) {
this.resource = null;
this.wpEditing.updateValue(this.workPackage.id, this);
this.wpEditing.updateValue(this.workPackage.id!, this);
return;
}
@ -361,6 +361,6 @@ export class WorkPackageChangeset {
resource.overriddenSchema = this.schema;
this.resource = (resource as WorkPackageResource);
this.wpEditing.updateValue(this.workPackage.id, this);
this.wpEditing.updateValue(this.workPackage.id!, this);
}
}

@ -80,7 +80,7 @@ export class WorkPackageEditForm {
public workPackage:WorkPackageResource,
public editMode:boolean = false) {
this.wpSubscription = this.wpCacheService.state(workPackage.id)
this.wpSubscription = this.wpCacheService.state(workPackage.id!)
.values$()
.subscribe((wp:WorkPackageResource) => {
this.workPackage = wp;
@ -183,8 +183,7 @@ export class WorkPackageEditForm {
this.editMode = false;
this.editContext.onSaved(isInitial, savedWorkPackage);
this.wpTableRefresh.request(
`Saved work package ${savedWorkPackage.id}`,
isInitial ? 'create' : 'update'
`Saved work package ${savedWorkPackage.id}`
);
})
.catch((error:ErrorResource|Object) => {

@ -66,7 +66,7 @@ export class WorkPackageEditingService extends StateCacheService<WorkPackageChan
* @return {WorkPackageChangeset} changeset or null if the associated work package id does not exist
*/
public changesetFor(oldReference:WorkPackageResource):WorkPackageChangeset {
const wpId = oldReference.id;
const wpId = oldReference.id!;
const workPackage = this.wpCacheService.state(wpId).getValueOr(oldReference);
const state = this.multiState.get(wpId);

@ -106,7 +106,7 @@ export class WorkPackageEditFieldGroupComponent implements OnInit, OnDestroy {
ngOnInit() {
const context = new SingleViewEditContext(this.injector, this);
this.form = WorkPackageEditForm.createInContext(this.injector, context, this.workPackage, this.initializeEditMode);
this.states.workPackages.get(this.workPackage.id)
this.states.workPackages.get(this.workPackage.id!)
.values$()
.pipe(
takeUntil(componentDestroyed(this)),
@ -151,7 +151,7 @@ export class WorkPackageEditFieldGroupComponent implements OnInit, OnDestroy {
field.activateOnForm(true);
} else {
this.states.workPackages
.get(this.workPackage.id)
.get(this.workPackage.id!)
.valuesPromise()
.then(wp => this.updateDisplayField(field, wp!));
}
@ -174,7 +174,7 @@ export class WorkPackageEditFieldGroupComponent implements OnInit, OnDestroy {
public stop() {
this.form.editMode = false;
this.wpEditing.stopEditing(this.workPackage.id);
this.wpEditing.stopEditing(this.workPackage.id!);
this.form.destroy();
}
@ -193,7 +193,7 @@ export class WorkPackageEditFieldGroupComponent implements OnInit, OnDestroy {
if (this.successState) {
this.$state.go(this.successState, {workPackageId: savedWorkPackage.id})
.then(() => {
this.wpTableFocus.updateFocus(savedWorkPackage.id);
this.wpTableFocus.updateFocus(savedWorkPackage.id!);
this.wpNotificationsService.showSave(savedWorkPackage, isInitial);
});
}

@ -146,7 +146,7 @@ export class WorkPackageNotificationService {
type: 'error',
link: {
text: this.I18n.t('js.work_packages.error.update_conflict_refresh'),
target: () => this.wpCacheService.require(workPackage.id, true)
target: () => this.wpCacheService.require(workPackage.id!, true)
}
});

@ -44,7 +44,7 @@ export class HighlightingRenderPass {
return;
}
const id = property.getId();
const id = property.id!;
const element:HTMLElement = this.tablePass.tableBody.children[position] as HTMLElement;
element.classList.add(Highlighting.rowClass(highlightAttribute, id));

@ -104,8 +104,8 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
const parent = ancestorChain[i];
const child = ancestorChain[i + 1];
const inTable = this.workPackageTable.originalRowIndex[parent.id];
const alreadyRendered = this.rendered[parent.id];
const inTable = this.workPackageTable.originalRowIndex[parent.id!];
const alreadyRendered = this.rendered[parent.id!];
if (alreadyRendered) {
// parent is already rendered.
@ -115,14 +115,14 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
if (inTable) {
// Get the current elements
let elements = this.deferred[parent.id] || [];
let elements = this.deferred[parent.id!] || [];
// Append to them the child and all children below
let newElements:WorkPackageResource[] = ancestorChain.slice(i + 1, ancestorChain.length);
newElements = newElements.map(child => this.wpCacheService.state(child.id).value!);
newElements = newElements.map(child => this.wpCacheService.state(child.id!).value!);
// Append all new elements
elements = elements.concat(newElements);
// Remove duplicates (Regression #29652)
this.deferred[parent.id] = _.uniqBy(elements, el => el.id);
this.deferred[parent.id!] = _.uniqBy(elements, el => el.id!);
return true;
}
// Otherwise, continue the chain upwards
@ -138,7 +138,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
* @param workPackage
*/
private renderAllDeferredChildren(workPackage:WorkPackageResource) {
const wpId = workPackage.id.toString();
const wpId = workPackage.id!;
const deferredChildren = this.deferred[wpId] || [];
// If the work package has deferred children to render,
@ -152,7 +152,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
}
private getOrBuildRow(workPackage:WorkPackageResource) {
let row:WorkPackageTableRow = this.workPackageTable.originalRowIndex[workPackage.id];
let row:WorkPackageTableRow = this.workPackageTable.originalRowIndex[workPackage.id!];
if (!row) {
row = {object: workPackage} as WorkPackageTableRow;
@ -168,12 +168,12 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
// Iterate ancestors
ancestors.forEach((el:WorkPackageResource, index:number) => {
const ancestor = this.states.workPackages.get(el.id).value!;
const ancestor = this.states.workPackages.get(el.id!).value!;
// If we see the parent the first time,
// build it as an additional row and insert it into the ancestry
if (!this.rendered[ancestor.id]) {
if (!this.rendered[ancestor.id!]) {
let [ancestorRow, hidden] = this.rowBuilder.buildAncestorRow(ancestor, ancestorGroups, index);
// Insert the ancestor row, either right here if it's a root node
// Or below the appropriate parent
@ -189,13 +189,13 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
}
// Remember we just added this extra ancestor row
this.additionalParents[ancestor.id] = ancestor;
this.additionalParents[ancestor.id!] = ancestor;
}
// Push the correct ancestor groups for identifiying a hierarchy group
ancestorGroups.push(hierarchyGroupClass(ancestor.id));
ancestorGroups.push(hierarchyGroupClass(ancestor.id!));
ancestors.slice(0, index).forEach((previousAncestor) => {
ancestorGroups.push(hierarchyGroupClass(previousAncestor.id));
ancestorGroups.push(hierarchyGroupClass(previousAncestor.id!));
});
});
@ -221,19 +221,19 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
* @param hidden
*/
private markRendered(workPackage:WorkPackageResource, hidden:boolean = false, isAncestor:boolean = false) {
this.rendered[workPackage.id] = true;
this.rendered[workPackage.id!] = true;
this.renderedOrder.push(this.buildRenderInfo(workPackage, hidden, isAncestor));
}
public ancestorClasses(workPackage:WorkPackageResource) {
const rowClasses = [hierarchyRootClass(workPackage.id)];
const rowClasses = [hierarchyRootClass(workPackage.id!)];
if (_.isArray(workPackage.ancestors)) {
workPackage.ancestors.forEach((ancestor) => {
rowClasses.push(hierarchyGroupClass(ancestor.id));
rowClasses.push(hierarchyGroupClass(ancestor.id!));
if (this.hierarchies.collapsed[ancestor.id]) {
rowClasses.push(collapsedGroupClass(ancestor.id));
if (this.hierarchies.collapsed[ancestor.id!]) {
rowClasses.push(collapsedGroupClass(ancestor.id!));
}
});
@ -262,7 +262,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
this.buildRenderInfo(workPackage, hidden, isAncestor)
);
this.rendered[workPackage.id] = true;
this.rendered[workPackage.id!] = true;
}
private buildRenderInfo(workPackage:WorkPackageResource, hidden:boolean, isAncestor:boolean):RowRenderInfo {
@ -274,7 +274,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
if (isAncestor) {
info.additionalClasses = [additionalHierarchyRowClassName].concat(this.ancestorClasses(workPackage));
info.classIdentifier = ancestorClassIdentifier(workPackage.id);
info.classIdentifier = ancestorClassIdentifier(workPackage.id!);
} else {
info.additionalClasses = this.ancestorClasses(workPackage);
info.classIdentifier = this.rowBuilder.classIdentifier(workPackage);

@ -59,9 +59,9 @@ export class SingleHierarchyRowBuilder extends SingleRowBuilder {
workPackage.ancestors.forEach((ancestor:WorkPackageResource) => {
element.classList.add(`__hierarchy-group-${ancestor.id}`);
if (state.collapsed[ancestor.id]) {
if (state.collapsed[ancestor.id!]) {
hidden = true;
element.classList.add(collapsedGroupClass(ancestor.id));
element.classList.add(collapsedGroupClass(ancestor.id!));
}
});
@ -77,7 +77,7 @@ export class SingleHierarchyRowBuilder extends SingleRowBuilder {
ancestorGroups:string[],
index:number):[HTMLElement, boolean] {
const workPackage = this.states.workPackages.get(ancestor.id).value!;
const workPackage = this.states.workPackages.get(ancestor.id!).value!;
const [tr, hidden] = this.buildEmpty(workPackage);
tr.classList.add(additionalHierarchyRowClassName);
return [tr, hidden];
@ -106,7 +106,7 @@ export class SingleHierarchyRowBuilder extends SingleRowBuilder {
*/
private buildHierarchyIndicator(workPackage:WorkPackageResource, jRow:JQuery | null, level:number):HTMLElement {
const hierarchyIndicator = document.createElement('span');
const collapsed = this.wpTableHierarchies.collapsed(workPackage.id);
const collapsed = this.wpTableHierarchies.collapsed(workPackage.id!);
const indicatorWidth = 25 + (20 * level) + 'px';
hierarchyIndicator.classList.add(hierarchyCellClassName);
hierarchyIndicator.style.width = indicatorWidth;

@ -25,8 +25,8 @@ export class RelationCellbuilder {
td.dataset['columnId'] = column.id;
// Get current expansion and value state
const expanded = this.wpTableRelationColumns.getExpandFor(workPackage.id) === column.id;
const relationState = this.wpRelations.state(workPackage.id).value;
const expanded = this.wpTableRelationColumns.getExpandFor(workPackage.id!) === column.id;
const relationState = this.wpRelations.state(workPackage.id!).value;
const relations = this.wpTableRelationColumns.relationsForColumn(workPackage,
relationState,
column);

@ -70,7 +70,7 @@ export class RelationRowBuilder extends SingleRowBuilder {
public createEmptyRelationRow(from:WorkPackageResource, to:WorkPackageResource) {
const identifier = this.relationClassIdentifier(from, to);
let tr = document.createElement('tr');
tr.dataset['workPackageId'] = to.id;
tr.dataset['workPackageId'] = to.id!;
tr.dataset['classIdentifier'] = identifier;
tr.classList.add(
@ -78,14 +78,14 @@ export class RelationRowBuilder extends SingleRowBuilder {
`wp-table--relations-aditional-row`,
identifier,
`${identifier}-table`,
relationGroupClass(from.id)
relationGroupClass(from.id!)
);
return tr;
}
public relationClassIdentifier(from:WorkPackageResource, to:WorkPackageResource) {
return relationIdentifier(to.id, from.id);
return relationIdentifier(to.id!, from.id!);
}
/**
@ -104,7 +104,7 @@ export class RelationRowBuilder extends SingleRowBuilder {
}
// Add the WP type label if this is a "<Relation Type> Relations" column
if (type === 'ofType') {
const wp = this.states.workPackages.get(denormalized.target.id).value!;
const wp = this.states.workPackages.get(denormalized.target.id!).value!;
typeLabel = wp.type.name;
}

@ -53,7 +53,7 @@ export class RelationsRenderPass {
// If the work package has no relations, ignore
const workPackage = row.workPackage;
const fromId = workPackage.id;
const fromId = workPackage.id!;
const state = this.wpRelations.state(fromId);
if (!state.hasValue() || _.size(state.value) === 0) {
return;

@ -114,7 +114,7 @@ export class SingleRowBuilder {
const identifier = this.classIdentifier(workPackage);
let tr = document.createElement('tr');
tr.setAttribute('tabindex', '0');
tr.dataset['workPackageId'] = workPackage.id;
tr.dataset['workPackageId'] = workPackage.id!;
tr.dataset['classIdentifier'] = identifier;
tr.classList.add(
tableRowClassName,
@ -178,13 +178,13 @@ export class SingleRowBuilder {
}
protected isColumnBeingEdited(workPackage:WorkPackageResource, column:QueryColumn) {
const form = this.workPackageTable.editing.forms[workPackage.id];
const form = this.workPackageTable.editing.forms[workPackage.id!];
return form && form.activeFields[column.id];
}
protected buildEmptyRow(workPackage:WorkPackageResource, row:HTMLElement):[HTMLElement, boolean] {
const changeset = this.workPackageTable.editing.changeset(workPackage.id);
const changeset = this.workPackageTable.editing.changeset(workPackage.id!);
let cells:{ [attribute:string]:JQuery } = {};
if (changeset && !changeset.empty) {
@ -213,7 +213,7 @@ export class SingleRowBuilder {
});
// Set the row selection state
if (this.wpTableSelection.isSelected(workPackage.id)) {
if (this.wpTableSelection.isSelected(workPackage.id!)) {
row.classList.add(checkedClassName);
}

@ -32,9 +32,8 @@ export class DragAndDropTransformer {
this.inlineCreateService.newInlineWorkPackageCreated
.pipe(takeUntil(this.querySpace.stopAllSubscriptions))
.subscribe((wpId) => {
this.reorderService
.add(this.querySpace, wpId)
.then(() => this.wpTableRefresh.request('Drag and Drop added item'));
this.reorderService.add(this.currentOrder, wpId);
this.wpTableRefresh.request('Drag and Drop added item');
});
this.querySpace.stopAllSubscriptions
@ -52,22 +51,25 @@ export class DragAndDropTransformer {
},
onMoved: (row:HTMLTableRowElement) => {
const wpId:string = row.dataset.workPackageId!;
this.reorderService
.move(this.querySpace, wpId, row.rowIndex - 1)
.then(() => this.wpTableRefresh.request('Drag and Drop moved item'));
this.reorderService.move(this.currentOrder, wpId, row.rowIndex - 1);
this.wpTableRefresh.request('Drag and Drop moved item');
},
onRemoved: (row:HTMLTableRowElement) => {
const wpId:string = row.dataset.workPackageId!;
this.reorderService
.remove(this.querySpace, wpId)
.then(() => this.wpTableRefresh.request('Drag and Drop removed item'));
this.reorderService.remove(this.currentOrder, wpId);
this.wpTableRefresh.request('Drag and Drop moved item');
},
onAdded: (row:HTMLTableRowElement) => {
const wpId:string = row.dataset.workPackageId!;
this.reorderService
.add(this.querySpace, wpId, row.rowIndex - 1)
.then(() => this.wpTableRefresh.request('Drag and Drop added item'));
this.reorderService.add(this.currentOrder, wpId, row.rowIndex - 1);
this.wpTableRefresh.request('Drag and Drop moved item');
}
});
}
protected get currentOrder():string[] {
return this.querySpace
.results
.mapOr((results) => results.elements.map(el => el.id!), []);
}
}

@ -32,6 +32,6 @@ export function hasChildrenInTable(workPackage:WorkPackageResource, table:WorkPa
return !!_.find(table.originalRows, (wpId:string) => {
const row = table.originalRowIndex[wpId].object;
return row.ancestorIds.indexOf(workPackage.id.toString()) >= 0;
return row.ancestorIds.indexOf(workPackage.id!) >= 0;
});
}

@ -52,7 +52,7 @@ export class WorkPackageTableAdditionalElementsService {
public initialize(rows:WorkPackageResource[]) {
// Add relations to the stack
Promise.all([
this.requireInvolvedRelations(rows.map(el => el.id)),
this.requireInvolvedRelations(rows.map(el => el.id!)),
this.requireHierarchyElements(rows)
]).then((results:string[][]) => {
this.loadAdditional(_.flatten(results));

@ -88,7 +88,7 @@ export class WorkPackageTableRelationColumnsService extends WorkPackageTableBase
}
// Only if the work package has anything expanded
const expanded = this.getExpandFor(workPackage.id);
const expanded = this.getExpandFor(workPackage.id!);
if (expanded === undefined) {
return;
}

@ -72,7 +72,7 @@ export class WorkPackageTable {
private buildIndex(rows:WorkPackageResource[]) {
this.originalRowIndex = {};
this.originalRows = rows.map((wp:WorkPackageResource, i:number) => {
let wpId = wp.id;
let wpId = wp.id!;
this.originalRowIndex[wpId] = <WorkPackageTableRow> {object: wp, workPackageId: wpId, position: i};
return wpId;
});
@ -123,7 +123,7 @@ export class WorkPackageTable {
}
_.each(pass.renderedOrder, (row) => {
if (row.workPackage && row.workPackage.id === workPackage.id) {
if (row.workPackage && row.workPackage.id === workPackage.id!) {
debugLog(`Refreshing rendered row ${row.classIdentifier}`);
row.workPackage = workPackage;
pass.refresh(row, workPackage, this.tbody);

@ -37,7 +37,7 @@ export class WorkPackageTableEditingContext {
}
public startEditing(workPackage:WorkPackageResource, classIdentifier:string):WorkPackageEditForm {
const wpId = workPackage.id;
const wpId = workPackage.id!;
const existing = this.forms[wpId];
if (existing) {
return existing;

@ -64,8 +64,8 @@ export class InlineCreateRowBuilder extends SingleRowBuilder {
public createEmptyRow(workPackage:WorkPackageResource) {
const identifier = this.classIdentifier(workPackage);
const tr = document.createElement('tr');
tr.id = rowId(workPackage.id);
tr.dataset['workPackageId'] = workPackage.id;
tr.id = rowId(workPackage.id!);
tr.dataset['workPackageId'] = workPackage.id!;
tr.dataset['classIdentifier'] = identifier;
tr.classList.add(
inlineCreateRowClassName, commonRowClassName, tableRowClassName, 'issue',

@ -175,11 +175,11 @@ export class WorkPackageInlineCreateComponent implements OnInit, OnChanges, OnDe
// Split view on the last inserted id if any
if (!this.table.configuration.isEmbedded) {
this.wpTableFocus.updateFocus(wp.id);
this.wpTableFocus.updateFocus(wp.id!);
}
// Notify inline create service
this.wpInlineCreate.newInlineWorkPackageCreated.next(wp.id);
this.wpInlineCreate.newInlineWorkPackageCreated.next(wp.id!);
} else {
// Remove current row
this.wpCreate.cancelCreation();

@ -38,7 +38,7 @@ export class WorkPackagesListChecksumService {
protected $state:StateService) {
}
public id:number | null;
public id:string|null;
public checksum:string|null;
public visibleChecksum:string|null;
@ -84,7 +84,7 @@ export class WorkPackagesListChecksumService {
return this.isOutdated(query.id, newQueryChecksum);
}
public executeIfOutdated(newId:number,
public executeIfOutdated(newId:string,
newChecksum:string|null,
callback:Function) {
if (this.isUninitialized() || this.isOutdated(newId, newChecksum)) {
@ -94,7 +94,7 @@ export class WorkPackagesListChecksumService {
}
}
private set(id:number | null, checksum:string | null) {
private set(id:string|null, checksum:string|null) {
this.id = id;
this.checksum = checksum;
}
@ -109,7 +109,7 @@ export class WorkPackagesListChecksumService {
return !this.id && !this.checksum;
}
private isIdDifferent(otherId:number | null) {
private isIdDifferent(otherId:string|null) {
return this.id !== otherId;
}
@ -117,7 +117,7 @@ export class WorkPackagesListChecksumService {
return this.checksum && otherChecksum !== this.checksum;
}
private isOutdated(otherId:number | null, otherChecksum:string | null) {
private isOutdated(otherId:string|null, otherChecksum:string|null) {
const hasCurrentQueryID = !!this.id;
const hasCurrentChecksum = !!this.checksum;
const idChanged = (this.id !== otherId);
@ -143,7 +143,7 @@ export class WorkPackagesListChecksumService {
return this.UrlParamsHelper.encodeQueryJsonParams(query, _.pick(pagination, ['page', 'perPage']));
}
private maintainUrlQueryState(id:string | number | null, checksum:string | null) {
private maintainUrlQueryState(id:string|null, checksum:string|null) {
this.visibleChecksum = checksum;
this.$state.go('.', {query_props: checksum, query_id: id}, {custom: {notify: false}});

@ -49,7 +49,7 @@ import {input} from "reactivestates";
import {catchError, mergeMap, share, switchMap, take, tap} from "rxjs/operators";
export interface QueryDefinition {
queryParams:{ query_id?:number, query_props?:string };
queryParams:{ query_id?:string, query_props?:string };
projectIdentifier?:string;
}
@ -105,7 +105,7 @@ export class WorkPackagesListService {
* @param queryParams
* @param projectIdentifier
*/
private streamQueryRequest(queryParams:{ query_id?:number, query_props?:string }, projectIdentifier ?:string):Observable<QueryResource> {
private streamQueryRequest(queryParams:{ query_id?:string, query_props?:string }, projectIdentifier ?:string):Observable<QueryResource> {
const decodedProps = this.getCurrentQueryProps(queryParams);
const queryData = this.UrlParamsHelper.buildV3GetQueryFromJsonParams(decodedProps);
const stream = this.QueryDm.stream(queryData, queryParams.query_id, projectIdentifier);
@ -123,7 +123,7 @@ export class WorkPackagesListService {
* Load a query.
* The query is either a persisted query, identified by the query_id parameter, or the default query. Both will be modified by the parameters in the query_props parameter.
*/
public fromQueryParams(queryParams:{ query_id?:number, query_props?:string }, projectIdentifier ?:string):Observable<QueryResource> {
public fromQueryParams(queryParams:{ query_id?:string, query_props?:string }, projectIdentifier ?:string):Observable<QueryResource> {
this.queryRequests.clear();
this.queryRequests.putValue({ queryParams: queryParams, projectIdentifier: projectIdentifier });
@ -169,7 +169,7 @@ export class WorkPackagesListService {
.catch((error) => {
let projectIdentifier = query.project && query.project.id;
return this.handleQueryLoadingError(error, {}, query.id, projectIdentifier);
return this.handleQueryLoadingError(error, {}, query.id!, projectIdentifier);
});
}
@ -239,7 +239,7 @@ export class WorkPackagesListService {
// Reload the query, and then reload the menu
this.reloadQuery(query).then(() => {
this.states.changes.queries.next(query.id);
this.states.changes.queries.next(query.id!);
});
return query;
@ -267,7 +267,7 @@ export class WorkPackagesListService {
this.loadDefaultQuery(id);
this.states.changes.queries.next(query.id);
this.states.changes.queries.next(query.id!);
});
@ -279,14 +279,14 @@ export class WorkPackagesListService {
let form = this.querySpace.queryForm.value!;
let promise = this.QueryDm.update(query, form);
let promise = this.QueryDm.update(query, form).toPromise();
promise
.then(() => {
this.NotificationsService.addSuccess(this.I18n.t('js.notice_successful_update'));
this.$state.go('.', { query_id: query!.id, query_props: null }, { reload: true });
this.states.changes.queries.next(query!.id);
this.states.changes.queries.next(query!.id!);
})
.catch((error:ErrorResource) => {
this.NotificationsService.addError(error.message);
@ -303,7 +303,7 @@ export class WorkPackagesListService {
this.NotificationsService.addSuccess(this.I18n.t('js.notice_successful_update'));
this.states.changes.queries.next(query!.id);
this.states.changes.queries.next(query!.id!);
});
return promise;
@ -338,7 +338,7 @@ export class WorkPackagesListService {
private updateStatesFromWPListOnPromise(query:QueryResource, promise:Promise<WorkPackageCollectionResource>):Promise<WorkPackageCollectionResource> {
return promise.then((results) => {
this.querySpace.ready.doAndTransition('Query loaded', () => {
this.wpStatesInitialization.updatequerySpace(query, results);
this.wpStatesInitialization.updateQuerySpace(query, results);
this.wpStatesInitialization.updateChecksum(query, results);
return this.querySpace.tableRendering.onQueryUpdated.valuesPromise();
});
@ -351,7 +351,7 @@ export class WorkPackagesListService {
return this.querySpace.query.value!;
}
private handleQueryLoadingError(error:ErrorResource, queryProps:any, queryId?:number, projectIdentifier?:string):Promise<QueryResource> {
private handleQueryLoadingError(error:ErrorResource, queryProps:any, queryId?:string, projectIdentifier?:string|null):Promise<QueryResource> {
this.NotificationsService.addError(this.I18n.t('js.work_packages.faulty_query.description'), error.message);
return new Promise((resolve, reject) => {

@ -56,7 +56,7 @@ export class WorkPackageStatesInitializationService {
this.initializeFromQuery(query, results);
// Update the (local) table states
this.updatequerySpace(query, results);
this.updateQuerySpace(query, results);
// Ensure checksum for state is correct
this.updateChecksum(query, results);
@ -83,7 +83,7 @@ export class WorkPackageStatesInitializationService {
this.states.queries.groupBy.putValue(schema.groupBy.allowedValues);
}
public updatequerySpace(query:QueryResource, results:WorkPackageCollectionResource) {
public updateQuerySpace(query:QueryResource, results:WorkPackageCollectionResource) {
// Clear table required data states
this.querySpace.additionalRequiredWorkPackages.clear('Clearing additional WPs before updating rows');
@ -96,7 +96,9 @@ export class WorkPackageStatesInitializationService {
this.querySpace.rows.putValue(results.elements);
this.wpCacheService.updateWorkPackageList(results.elements);
this.authorisationService.initModelAuth('work_packages', results.$links);
results.elements.forEach(wp => this.wpCacheService.updateWorkPackage(wp, true));
this.querySpace.results.putValue(results);

@ -82,7 +82,7 @@ export class UrlParamsHelperService {
private encodeColumns(paramsData:any, query:QueryResource) {
paramsData.c = query.columns.map(function (column) {
return column.id;
return column.id!;
})
return paramsData;
}
@ -115,7 +115,7 @@ export class UrlParamsHelperService {
paramsData.t = query
.sortBy
.map(function (sort:QuerySortByResource) {
return sort.id.replace('-', ':')
return sort.id!.replace('-', ':')
})
.join();
}

@ -83,7 +83,7 @@ export class WorkPackageChildrenQueryComponent extends WorkPackageRelationQueryB
// Refresh table when work package is refreshed
this.wpCacheService
.observe(this.workPackage.id)
.observe(this.workPackage.id!)
.pipe(
untilComponentDestroyed(this)
)

@ -67,8 +67,8 @@ export class WpRelationInlineAddExistingComponent {
this.wpInlineCreate.add(this.workPackage, newRelationId)
.then(() => {
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
this.wpTableRefresh.request(`Added relation ${newRelationId}`, 'reference', { visible: true });
this.wpCacheService.loadWorkPackage(this.workPackage.id!, true);
this.wpTableRefresh.request(`Added relation ${newRelationId}`, { visible: true });
this.isDisabled = false;
this.wpInlineCreate.newInlineWorkPackageReferenced.next(newRelationId);
this.cancel();

@ -58,7 +58,7 @@ export class WpRelationInlineCreateService extends WorkPackageInlineCreateServic
* Add a new relation of the above type
*/
public add(from:WorkPackageResource, toId:string):Promise<unknown> {
return this.wpRelations.addCommonRelation(toId, this.relationType, from.id);
return this.wpRelations.addCommonRelation(toId, this.relationType, from.id!);
}
/**

@ -60,7 +60,7 @@ export class WorkPackageRelationQueryComponent extends WorkPackageRelationQueryB
'remove-relation-action',
this.I18n.t('js.relation_buttons.remove'),
(relatedTo:WorkPackageResource) => {
this.embeddedTable.loadingIndicator = this.wpRelations.require(relatedTo.id)
this.embeddedTable.loadingIndicator = this.wpRelations.require(relatedTo.id!)
.then(() => this.wpInlineCreate.remove(this.workPackage, relatedTo))
.then(() => this.refreshTable())
.catch((error) => this.wpNotifications.handleRawError(error, this.workPackage));
@ -96,7 +96,7 @@ export class WorkPackageRelationQueryComponent extends WorkPackageRelationQueryB
// When relations have changed, refresh this table
this.wpRelations.observe(this.workPackage.id)
this.wpRelations.observe(this.workPackage.id!)
.pipe(untilComponentDestroyed(this))
.subscribe(() => this.refreshTable());
}
@ -109,7 +109,7 @@ export class WorkPackageRelationQueryComponent extends WorkPackageRelationQueryB
this.wpInlineCreate
.add(this.workPackage, toId)
.then(() => {
this.wpTableRefresh.request(`Added relation ${toId}`, 'reference', { visible: true });
this.wpTableRefresh.request(`Added relation ${toId}`, { visible: true });
})
.catch(error => this.wpNotifications.handleRawError(error, this.workPackage));
}

@ -63,7 +63,7 @@ export abstract class WorkPackageRelationQueryBase {
_.each(duppedQuery.filters, (filter) => {
if (filter._links.values[0] && filter._links.values[0].templated) {
filter._links.values[0].href = filter._links.values[0].href.replace('{id}', this.workPackage.id);
filter._links.values[0].href = filter._links.values[0].href.replace('{id}', this.workPackage.id!);
}
});

@ -72,7 +72,7 @@ export class WorkPackageRelationRowComponent implements OnInit, OnDestroy {
{'name': this.relation.normalizedType(this.workPackage)})!;
this.wpCacheService
.observe(this.relatedWorkPackage.id)
.observe(this.relatedWorkPackage.id!)
.pipe(
untilComponentDestroyed(this)
).subscribe((wp) => {
@ -131,7 +131,6 @@ export class WorkPackageRelationRowComponent implements OnInit, OnDestroy {
this.userInputs.showDescriptionEditForm = false;
this.wpTableRefresh.request(
`Updated relation ${this.relatedWorkPackage.id}`,
'reference',
{visible: true}
);
this.wpNotificationsService.showSave(this.relatedWorkPackage);
@ -177,7 +176,6 @@ export class WorkPackageRelationRowComponent implements OnInit, OnDestroy {
.then(() => {
this.wpTableRefresh.request(
`Removed relation ${this.relatedWorkPackage.id}`,
'reference',
{ visible: true }
);
this.wpCacheService.updateWorkPackage(this.relatedWorkPackage);

@ -98,7 +98,7 @@ export class WpRelationsAutocompleteComponent implements OnInit, OnDestroy {
},
select: (evt, ui:any) => {
selected = true;
this.onSelect.emit(ui.item.workPackage.id);
this.onSelect.emit(ui.item.workPackage.id!);
},
minLength: 0
})

@ -54,11 +54,11 @@ export class WorkPackageRelationsCreateComponent {
}
protected createCommonRelation() {
return this.wpRelations.addCommonRelation(this.workPackage.id,
return this.wpRelations.addCommonRelation(this.workPackage.id!,
this.selectedRelationType,
this.selectedWpId)
.then(relation => {
this.wpTableRefresh.request(`Added relation ${relation.id}`, "reference", {visible: true});
this.wpTableRefresh.request(`Added relation ${relation.id}`, {visible: true});
this.wpNotificationsService.showSave(this.workPackage);
this.toggleRelationsCreateForm();
})

@ -63,7 +63,7 @@ export class WorkPackageRelationsHierarchyComponent implements OnInit, OnDestroy
};
ngOnInit() {
this.workPackagePath = this.PathHelper.workPackagePath(this.workPackage.id);
this.workPackagePath = this.PathHelper.workPackagePath(this.workPackage.id!);
this.canModifyHierarchy = !!this.workPackage.changeParent;
this.canAddRelation = !!this.workPackage.addRelation;
@ -73,7 +73,7 @@ export class WorkPackageRelationsHierarchyComponent implements OnInit, OnDestroy
showHierarchies: false
};
this.wpCacheService.loadWorkPackage(this.workPackage.id).values$()
this.wpCacheService.loadWorkPackage(this.workPackage.id!).values$()
.pipe(
takeUntil(componentDestroyed(this))
)

@ -72,7 +72,6 @@ export class WorkPackageRelationsHierarchyService {
this.wpNotificationsService.showSave(wp);
this.wpTableRefresh.request(
`Changed parent of ${workPackage.id} to ${parentId}`,
'reference',
{ visible: true }
);
return wp;
@ -91,12 +90,11 @@ export class WorkPackageRelationsHierarchyService {
return this.wpCacheService
.require(childWpId)
.then((wpToBecomeChild:WorkPackageResource | undefined) => {
return this.changeParent(wpToBecomeChild!, workPackage.id)
return this.changeParent(wpToBecomeChild!, workPackage.id!)
.then(wp => {
this.wpCacheService.loadWorkPackage(workPackage.id.toString(), true);
this.wpCacheService.loadWorkPackage(workPackage.id!, true);
this.wpTableRefresh.request(
`Added new child to ${workPackage.id}`,
'reference',
{ visible: true });
return wp;
});
@ -132,7 +130,7 @@ export class WorkPackageRelationsHierarchyService {
},
lockVersion: childWorkPackage.lockVersion
}).then(wp => {
this.wpCacheService.loadWorkPackage(parentWorkPackage.id.toString(), true);
this.wpCacheService.loadWorkPackage(parentWorkPackage.id!, true);
this.wpCacheService.updateWorkPackage(wp);
})
.catch((error) => {

@ -67,7 +67,7 @@ export class WorkPackageRelationsComponent implements OnInit, OnDestroy {
ngOnInit() {
this.canAddRelation = !!this.workPackage.addRelation;
this.wpRelations.state(this.workPackage.id).values$()
this.wpRelations.state(this.workPackage.id!).values$()
.pipe(
takeUntil(componentDestroyed(this))
)
@ -75,10 +75,10 @@ export class WorkPackageRelationsComponent implements OnInit, OnDestroy {
this.loadedRelations(relations);
});
this.wpRelations.require(this.workPackage.id);
this.wpRelations.require(this.workPackage.id!);
// Listen for changes to this WP.
this.wpCacheService.loadWorkPackage(this.workPackage.id).values$()
this.wpCacheService.loadWorkPackage(this.workPackage.id!).values$()
.pipe(
takeUntil(componentDestroyed(this))
)
@ -148,7 +148,7 @@ export class WorkPackageRelationsComponent implements OnInit, OnDestroy {
)
.subscribe((relatedWorkPackages:WorkPackageResource[]) => {
this.currentRelations = relatedWorkPackages.map((wp:WorkPackageResource) => {
wp.relatedBy = relations[wp.id];
wp.relatedBy = relations[wp.id!];
return wp;
});

@ -5,11 +5,9 @@ import {PathHelperService} from 'core-app/modules/common/path-helper/path-helper
import {multiInput, MultiInputState, StatesGroup} from 'reactivestates';
import {StateCacheService} from '../states/state-cache.service';
import {Injectable} from "@angular/core";
import {WorkPackageTableRefreshService} from '../wp-table/wp-table-refresh-request.service';
import {HalResource} from "core-app/modules/hal/resources/hal-resource";
import {HalResourceService} from "core-app/modules/hal/services/hal-resource.service";
export type RelationsStateValue = { [relationId:number]:RelationResource };
export type RelationsStateValue = { [relationId:string]:RelationResource };
export class RelationStateGroup extends StatesGroup {
name = 'WP-Relations';
@ -72,7 +70,7 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
* Find a given relation by from, to and relation Type
*/
public find(from:WorkPackageResource, to:WorkPackageResource, type:string):RelationResource|undefined {
const relations:RelationsStateValue|undefined = this.state(from.id).value;
const relations:RelationsStateValue|undefined = this.state(from.id!).value;
if (!relations) {
return;
@ -83,7 +81,7 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
// Check that
// 1. the denormalized relation points at "to"
// 2. that the denormalized relation type matches.
return denormalized.target.id.toString() === to.id.toString() &&
return denormalized.target.id === to.id &&
denormalized.relationType === type;
});
}
@ -147,11 +145,11 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
private insertIntoStates(relation:RelationResource) {
_.values(relation.ids).forEach(wpId => {
this.multiState.get(wpId).doModify((value:RelationsStateValue) => {
value[relation.id] = relation;
value[relation.id!] = relation;
return value;
}, () => {
let value:RelationsStateValue = {};
value[relation.id] = relation;
value[relation.id!] = relation;
return value;
});
});
@ -164,7 +162,7 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
private removeFromStates(relation:RelationResource) {
_.values(relation.ids).forEach(wpId => {
this.multiState.get(wpId).doModify((value:RelationsStateValue) => {
delete value[relation.id];
delete value[relation.id!];
return value;
}, () => {
return {};
@ -181,7 +179,7 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
*/
private updateRelationsStateTo(wpId:string, relations:RelationResource[]) {
const state = this.multiState.get(wpId);
const relationsToInsert = _.keyBy(relations, r => r.id);
const relationsToInsert = _.keyBy(relations, r => r.id!);
state.putValue(relationsToInsert, 'Overriding relations state.');
}

@ -53,7 +53,7 @@ export class NewestActivityOnOverviewComponent extends ActivityPanelBaseControll
}
ngOnInit() {
this.workPackageId = this.workPackage.id;
this.workPackageId = this.workPackage.id!;
super.ngOnInit();
}

@ -55,7 +55,7 @@ export class WorkPackageRelationsTabComponent implements OnInit, OnDestroy {
takeUntil(componentDestroyed(this))
)
.subscribe((wp) => {
this.workPackageId = wp.id;
this.workPackageId = wp.id!;
this.workPackage = wp;
});
}

@ -122,7 +122,7 @@ export class WorkPackageWatchersTabComponent implements OnInit, OnDestroy {
// Forcefully reload the resource to update the watch/unwatch links
// should the current user have been added
this.wpWatchersService.require(this.workPackage, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id!, true);
})
.catch((error:any) => this.wpNotificationsService.showError(error, this.workPackage));
}
@ -137,7 +137,7 @@ export class WorkPackageWatchersTabComponent implements OnInit, OnDestroy {
// Forcefully reload the resource to update the watch/unwatch links
// should the current user have been removed
this.wpWatchersService.require(this.workPackage, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id, true);
this.wpCacheService.loadWorkPackage(this.workPackage.id!, true);
})
.catch((error:any) => this.wpNotificationsService.showError(error, this.workPackage));
}

@ -51,7 +51,7 @@ export abstract class WorkPackageLinkedResourceCache<T> {
* @returns {Promise<T>}
*/
public require(workPackage:WorkPackageResource, force:boolean = false):Promise<T> {
const id = workPackage.id.toString();
const id = workPackage.id!;
const state = this.cache.state;
// Clear cache if requesting different resource
@ -74,8 +74,8 @@ export abstract class WorkPackageLinkedResourceCache<T> {
.toPromise();
}
public clear(workPackageId:string|number) {
if (this.cache.id === workPackageId.toString()) {
public clear(workPackageId:string|null) {
if (this.cache.id === workPackageId) {
this.cache.state.clear();
}
}

@ -18,7 +18,7 @@ import {QueryFilterInstanceResource} from "core-app/modules/hal/resources/query-
templateUrl: './wp-embedded-table.html'
})
export class WorkPackageEmbeddedTableComponent extends WorkPackageEmbeddedBaseComponent implements OnInit, AfterViewInit, OnDestroy {
@Input('queryId') public queryId?:number;
@Input('queryId') public queryId?:string;
@Input('queryProps') public queryProps:any = {};
@Input() public tableActions:OpTableActionFactory[] = [];
@Input() public externalHeight:boolean = false;
@ -75,7 +75,7 @@ export class WorkPackageEmbeddedTableComponent extends WorkPackageEmbeddedBaseCo
this.querySpace.ready.doAndTransition('Query loaded', () => {
this.wpStatesInitialization.clearStates();
this.wpStatesInitialization.initializeFromQuery(query, results);
this.wpStatesInitialization.updatequerySpace(query, results);
this.wpStatesInitialization.updateQuerySpace(query, results);
return this.querySpace.tableRendering.onQueryUpdated.valuesPromise()
.then(() => {

@ -18,7 +18,7 @@ export class OpDetailsTableAction extends OpTableAction {
public buildElement() {
// Append details button
let detailsLink = this.uiStatebuilder.linkToDetails(
this.workPackage.id,
this.workPackage.id!,
this.text.button,
''
);

@ -49,7 +49,7 @@ export class OpUnlinkTableAction extends OpTableAction {
element.title = this.title;
element.href = '#';
element.classList.add(contextColumnIcon, 'wp-table-action--unlink');
element.dataset.workPackageId = this.workPackage.id.toString();
element.dataset.workPackageId = this.workPackage.id!;
element.appendChild(opIconElement('icon', 'icon-close'));
jQuery(element).click((event) => {
event.preventDefault();

@ -259,7 +259,7 @@ export class TimelineCellRenderer {
if (renderInfo.viewParams.activeSelectionMode) {
element.style.backgroundImage = null; // required! unable to disable "fade out bar" with css
if (renderInfo.viewParams.selectionModeStart === '' + renderInfo.workPackage.id) {
if (renderInfo.viewParams.selectionModeStart === '' + renderInfo.workPackage.id!) {
jQuery(element).addClass(timelineMarkerSelectionStartClass);
element.style.background = null;
}
@ -369,8 +369,8 @@ export class TimelineCellRenderer {
element.style.backgroundColor = this.fallbackColor;
}
const id = type.getId();
element.classList.add(Highlighting.rowClass('type', id));
const id = type.id;
element.classList.add(Highlighting.rowClass('type', id!));
}
protected assignDate(changeset:WorkPackageChangeset, attributeName:string, value:moment.Moment) {

@ -265,17 +265,17 @@ export class WorkPackageTimelineTableController implements AfterViewInit, OnDest
}
startAddRelationPredecessor(start:WorkPackageResource) {
this.activateSelectionMode(start.id, end => {
this.activateSelectionMode(start.id!, end => {
this.wpRelations
.addCommonRelation(start.id, 'follows', end.id)
.addCommonRelation(start.id!, 'follows', end.id!)
.catch((error:any) => this.wpNotificationsService.handleRawError(error, end));
});
}
startAddRelationFollower(start:WorkPackageResource) {
this.activateSelectionMode(start.id, end => {
this.activateSelectionMode(start.id!, end => {
this.wpRelations
.addCommonRelation(start.id, 'precedes', end.id)
.addCommonRelation(start.id!, 'precedes', end.id!)
.catch((error:any) => this.wpNotificationsService.handleRawError(error, end));
});
}

@ -2,11 +2,7 @@ import {InputState} from 'reactivestates';
import {IsolatedQuerySpace} from "core-app/modules/work_packages/query-space/isolated-query-space";
import {Injectable} from '@angular/core';
export type WorkPackageTableRefreshOrigin = 'create'|'edit'|'update'|'delete'|'reference'|'other';
export interface WorkPackageTableRefreshRequest {
/** What origin the request came from */
origin:WorkPackageTableRefreshOrigin;
/** Whether the refresh should happen visibly */
visible:boolean;
/** Whether the first page should be requested */
@ -22,11 +18,10 @@ export class WorkPackageTableRefreshService {
/**
* Request a refresh to the work package table.
* @param reason a reason for logging purposes.
* @param origin The origin type for the refresh used for filtering.
* @param request WorkPackageTableRefreshRequest
*/
public request(reason:string, origin:WorkPackageTableRefreshOrigin = 'update', request:Partial<WorkPackageTableRefreshRequest> = {}) {
let req = { visible: false, firstPage: false, ...request, origin: origin };
public request(reason:string, request:Partial<WorkPackageTableRefreshRequest> = {}) {
let req = { visible: false, firstPage: false, ...request };
this.state.putValue(req, reason);
}

@ -70,7 +70,7 @@ export class BoardStatusActionService implements BoardActionService {
let filter = this.queryFilterBuilder.build(
'status',
'=',
[{ href: this.v3.statuses.id(value.id).toString() }]
[{ href: this.v3.statuses.id(value.id!).toString() }]
);
return this.boardListService.addQuery(board, params, [filter]);

@ -35,6 +35,6 @@ export class BoardCacheService extends StateCacheService<Board> {
}
update(board:Board) {
this.updateValue(board.id, board);
this.updateValue(board.id!, board);
}
}

@ -38,10 +38,8 @@
</div>
<div class="board-list--query-container">
<wp-card-view [queryId]="query.id"
<wp-card-view [dragAndDropEnabled]="dragAndDropEnabled"
[cardsRemovable]="board.isFree"
[queryProps]="columnsQueryProps"
[configuration]="boardTableConfiguration"
[highlightingMode]="board.highlightingMode">
</wp-card-view>
</div>

@ -29,6 +29,7 @@ import {AuthorisationService} from "core-app/modules/common/model-auth/model-aut
import {Highlighting} from "core-components/wp-fast-table/builders/highlighting/highlighting.functions";
import {WorkPackageCardViewComponent} from "core-components/wp-card-view/wp-card-view.component";
import {GonService} from "core-app/modules/common/gon/gon.service";
import {WorkPackageStatesInitializationService} from "core-components/wp-list/wp-states-initialization.service";
@Component({
selector: 'board-list',
@ -72,15 +73,8 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
areYouSure: this.I18n.t('js.text_are_you_sure'),
};
public boardTableConfiguration = {
hierarchyToggleEnabled: false,
columnMenuEnabled: false,
actionsColumnEnabled: false,
// Drag & Drop is enabled when editable
dragAndDropEnabled: false,
isEmbedded: true,
isCardView: true
};
/** Are we allowed to drag & drop elements ? */
public dragAndDropEnabled:boolean = false;
constructor(private readonly QueryDm:QueryDmService,
private readonly I18n:I18nService,
@ -90,6 +84,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
private readonly cdRef:ChangeDetectorRef,
private readonly querySpace:IsolatedQuerySpace,
private readonly Gon:GonService,
private readonly wpStatesInitialization:WorkPackageStatesInitializationService,
private readonly authorisationService:AuthorisationService,
private readonly wpInlineCreate:WorkPackageInlineCreateService,
private readonly loadingIndicator:LoadingIndicatorService) {
@ -97,7 +92,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
}
ngOnInit():void {
const boardId:number = this.state.params.board_id;
const boardId:string = this.state.params.board_id;
this.loadQuery();
@ -122,11 +117,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
untilComponentDestroyed(this)
)
.subscribe((board) => {
this.boardTableConfiguration = {
...this.boardTableConfiguration,
isCardView: board.displayMode === 'cards',
dragAndDropEnabled: board.editable,
};
this.dragAndDropEnabled = board.editable;
});
}
@ -160,7 +151,8 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
this.inFlight = true;
this.query.name = value;
this.QueryDm
.patch(this.query.id, {name: value})
.patch(this.query.id!, {name: value})
.toPromise()
.then(() => {
this.inFlight = false;
this.notifications.addSuccess(this.text.updateSuccessful);
@ -176,7 +168,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
return '';
}
const value = filter.values[0] as HalResource;
return Highlighting.rowClass(attribute, value.getId());
return Highlighting.rowClass(attribute, value.id!);
}
public get listName() {
@ -184,7 +176,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
}
private loadQuery() {
const queryId:number = this.resource.options.query_id as number;
const queryId:string = (this.resource.options.query_id as number|string).toString();
this.QueryDm
.stream(this.columnsQueryProps, queryId)
@ -192,7 +184,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni
withLoadingIndicator(this.indicatorInstance, 50),
)
.subscribe(query => {
this.querySpace.query.putValue(query);
this.wpStatesInitialization.updateQuerySpace(query, query.results);
});
}

@ -100,7 +100,7 @@ export class BoardService {
public delete(board:Board):Promise<void> {
return this.boardDm
.delete(board)
.then(() => this.boardCache.clearSome(board.id));
.then(() => this.boardCache.clearSome(board.id!));
}
/**

@ -117,7 +117,7 @@ export class BoardInlineAddAutocompleterComponent implements AfterContentInit {
filters.add('subjectOrId', '**', [query]);
if (rows.length > 0) {
filters.add('id', '!', rows.map((wp:WorkPackageResource) => wp.id));
filters.add('id', '!', rows.map((wp:WorkPackageResource) => wp.id!));
}
return this.halResourceService

@ -116,7 +116,7 @@ export class BoardsToolbarMenuDirective extends OpContextMenuTrigger implements
this.boardService
.delete(this.board)
.then(() => {
this.BoardCache.clearSome(this.board.id);
this.BoardCache.clearSome(this.board.id!);
this.State.go('^', { flash_message: { type: 'success', message: this.text.deleteSuccessful }});
});
}

@ -99,16 +99,9 @@ export class DragAndDropService implements OnDestroy {
}
protected initializeDrake(containers:Element[]) {
let dropping = false;
this.drake = dragula(containers, {
moves: (el:any, container:any, handle:any, sibling:any) => {
// Never move an item while another is being saved
if (dropping) {
return false;
}
let result = false;
this.members.forEach(member => {
if (member.container === container) {
@ -134,15 +127,11 @@ export class DragAndDropService implements OnDestroy {
});
this.drake.on('drop', async (el:HTMLElement, target:HTMLElement, source:HTMLElement, sibling:HTMLElement|null) => {
dropping = true;
try {
await this.handleDrop(el, target, source, sibling);
} catch (e) {
console.error("Failed to handle drop of %O", el);
}
dropping = false;
});
}

@ -9,6 +9,8 @@ import {IWorkPackageEditingServiceToken} from "core-components/wp-edit-form/work
import {WorkPackageEditingService} from "core-components/wp-edit-form/work-package-editing-service";
import {WorkPackageNotificationService} from "core-components/wp-edit/wp-notification.service";
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource";
import {Observable, of, throwError} from "rxjs";
import {QueryDmService} from "core-app/modules/hal/dm-services/query-dm.service";
@Injectable()
export class ReorderQueryService {
@ -16,6 +18,7 @@ export class ReorderQueryService {
constructor(readonly states:States,
readonly pathHelper:PathHelperService,
readonly injector:Injector,
readonly queryDm:QueryDmService,
@Inject(IWorkPackageEditingServiceToken) protected readonly wpEditing:WorkPackageEditingService,
readonly wpNotifications:WorkPackageNotificationService) {
}
@ -23,26 +26,22 @@ export class ReorderQueryService {
/**
* Move an item in the list
*/
public move(querySpace:IsolatedQuerySpace, wpId:string, toIndex:number):Promise<QueryResource|void> {
const order = this.getCurrentOrder(querySpace);
public move(order:string[], wpId:string, toIndex:number):string[] {
// Find index of the work package
let fromIndex = order.findIndex((id) => id === wpId);
order.splice(fromIndex, 1);
order.splice(toIndex, 0, wpId);
return this.updateQuery(querySpace.query.value, order);
return order;
}
/**
* Pull an item from the rendered list
*/
public remove(querySpace:IsolatedQuerySpace, wpId:string):Promise<QueryResource|void> {
const order = this.getCurrentOrder(querySpace);
public remove(order:string[], wpId:string):string[] {
_.remove(order, id => id === wpId);
return this.updateQuery(querySpace.query.value, order);
return order;
}
/**
@ -50,16 +49,14 @@ export class ReorderQueryService {
* @param querySpace
* @param toIndex index to add to or -1 to push to the end.
*/
public async add(querySpace:IsolatedQuerySpace, wpId:string, toIndex:number = -1) {
const order = this.getCurrentOrder(querySpace);
public add(order:string[], wpId:string, toIndex:number = -1):string[] {
if (toIndex === -1) {
order.push(wpId);
} else {
order.splice(toIndex, 0, wpId);
}
return this.updateQuery(querySpace.query.value, order);
return order;
}
public updateWorkPackage(querySpace:IsolatedQuerySpace, workPackage:WorkPackageResource) {
@ -77,22 +74,16 @@ export class ReorderQueryService {
return Promise.resolve();
}
protected getCurrentOrder(querySpace:IsolatedQuerySpace):string[] {
return querySpace
.renderedWorkPackages
.mapOr((rows) => rows.map(row => row.workPackageId!.toString()), []);
}
private updateQuery(query:QueryResource|undefined, orderedIds:string[]):Promise<QueryResource|void> {
public saveOrderInQuery(query:QueryResource|undefined, orderedIds:string[]):Observable<unknown> {
if (query && !!query.updateImmediately) {
const orderedWorkPackages = orderedIds
.map(id => this.pathHelper.api.v3.work_packages.id(id).toString());
debugLog("New order: " + orderedIds.join(", "));
return query.updateImmediately({orderedWorkPackages: orderedWorkPackages});
return this.queryDm.patch(query.id!, {orderedWorkPackages: orderedWorkPackages});
} else {
return Promise.reject("Query not writable");
return throwError("Query not writable");
}
}
}

@ -57,7 +57,7 @@ export class BoardsIndexPageComponent {
this.boardService
.delete(board)
.then(() => {
this.boardCache.clearSome(board.id);
this.boardCache.clearSome(board.id!);
this.notifications.addSuccess(this.text.deleteSuccessful);
})
.catch((error) => this.notifications.addError("Deletion failed: " + error));

@ -69,7 +69,7 @@ export const BOARDS_ROUTES:Ng2StateDeclaration[] = [
name: 'boards.show',
url: '/{board_id}',
params: {
board_id: { type: 'int' },
board_id: { type: 'string' },
isNew: { type: 'bool' }
},
component: BoardComponent,

@ -167,7 +167,7 @@ export class WorkPackagesCalendarController implements OnInit, OnDestroy {
title: workPackage.subject,
start: startDate,
end: endDate,
className: `__hl_row_type_${workPackage.type.getId()}`,
className: `__hl_row_type_${workPackage.type.id}`,
workPackage: workPackage
};
});

@ -51,7 +51,7 @@ export class HighlightedResourceDisplayField extends HighlightableDisplayField {
private addHighlight(element:HTMLElement):void {
if (this.attribute instanceof HalResource) {
const hlClass = Highlighting.dotClass(this.name, this.attribute.getId());
const hlClass = Highlighting.dotClass(this.name, this.attribute.id!);
element.classList.add(hlClass);
}
}

@ -73,7 +73,7 @@ export class EditFieldComponent extends Field implements OnInit, OnDestroy {
super();
this.initialize();
this.wpEditing.state(this.changeset.workPackage.id)
this.wpEditing.state(this.changeset.workPackage.id!)
.values$()
.pipe(
untilComponentDestroyed(this)

@ -126,7 +126,7 @@ export class FormattableEditFieldComponent extends EditFieldComponent implements
if (this.resource.isNew && this.resource.project) {
return this.resource.project.href;
} else if (!this.resource.isNew) {
return this.pathHelper.api.v3.work_packages.id(this.resource.id).path;
return this.pathHelper.api.v3.work_packages.id(this.resource.id!).path;
}
}

@ -209,7 +209,7 @@ export class GlobalSearchInputComponent implements OnInit, OnDestroy {
this.autocompleteWorkPackages(term).then((values) => {
this.results = this.suggestions.concat(values.map((wp:any) => {
return {
id: wp.id,
id: wp.id!,
subject: wp.subject,
status: wp.status.name,
statusId: wp.status.idFromLink,

@ -72,11 +72,11 @@ export class WidgetTimeEntriesCurrentUserComponent extends AbstractWidgetCompone
}
public workPackageName(entry:TimeEntryResource) {
return `#${entry.workPackage.idFromLink}: ${entry.workPackage.name}`;
return `#${entry.workPackage.id}: ${entry.workPackage.name}`;
}
public workPackageId(entry:TimeEntryResource) {
return entry.workPackage.idFromLink;
return entry.workPackage.id!;
}
public comment(entry:TimeEntryResource) {
@ -88,11 +88,11 @@ export class WidgetTimeEntriesCurrentUserComponent extends AbstractWidgetCompone
}
public editPath(entry:TimeEntryResource) {
return this.pathHelper.timeEntryEditPath(entry.id);
return this.pathHelper.timeEntryEditPath(entry.id!);
}
public deletePath(entry:TimeEntryResource) {
return this.pathHelper.timeEntryPath(entry.id);
return this.pathHelper.timeEntryPath(entry.id!);
}
public workPackagePath(entry:TimeEntryResource) {

@ -59,7 +59,7 @@ export class QueryDmService {
* @param queryId
* @param projectIdentifier
*/
public stream(queryData:Object, queryId?:number, projectIdentifier?:string):Observable<QueryResource> {
public stream(queryData:Object, queryId?:string, projectIdentifier?:string|null):Observable<QueryResource> {
let path:string;
if (queryId) {
@ -72,16 +72,16 @@ export class QueryDmService {
.get<QueryResource>(path, queryData);
}
public find(queryData:Object, queryId?:number, projectIdentifier?:string):Promise<QueryResource> {
public find(queryData:Object, queryId?:string, projectIdentifier?:string|null):Promise<QueryResource> {
return this.stream(queryData, queryId, projectIdentifier).toPromise();
}
public findDefault(queryData:Object, projectIdentifier?:string):Promise<QueryResource> {
public findDefault(queryData:Object, projectIdentifier?:string|null):Promise<QueryResource> {
return this.find(queryData, undefined, projectIdentifier);
}
public reload(query:QueryResource, pagination:PaginationObject):Promise<QueryResource> {
let path = this.pathHelper.api.v3.queries.id(query.id).toString();
let path = this.pathHelper.api.v3.queries.id(query.id!).toString();
return this.halResourceService
.get<QueryResource>(path, pagination)
@ -123,16 +123,15 @@ export class QueryDmService {
.toPromise();
}
public update(query:QueryResource, form:QueryFormResource) {
public update(query:QueryResource, form:QueryFormResource):Observable<QueryResource> {
const payload = this.extractPayload(query, form);
return this.patch(query.id, payload);
return this.patch(query.id!, payload);
}
public patch(id:string|number, payload:{[key:string]:unknown}) {
public patch(id:string, payload:{[key:string]:unknown}):Observable<QueryResource> {
let path:string = this.pathHelper.api.v3.queries.id(id).toString();
return this.halResourceService
.patch<QueryResource>(path, payload)
.toPromise();
.patch<QueryResource>(path, payload);
}
public create(query:QueryResource, form:QueryFormResource):Promise<QueryResource> {

@ -57,7 +57,7 @@ export class QueryFormDmService {
return query.$links.update(payload);
}
public loadWithParams(params:{}, queryId:number|undefined, projectIdentifier:string|undefined|null, payload:any = {}):Promise<QueryFormResource> {
public loadWithParams(params:{}, queryId:string|undefined, projectIdentifier:string|undefined|null, payload:any = {}):Promise<QueryFormResource> {
// We need a valid payload so that we
// can check whether form saving is possible.
// The query needs a name to be valid.

@ -35,7 +35,6 @@ export interface CustomActionResourceLinks {
}
export class CustomActionResource extends HalResource {
public id:number;
public name:string;
public description:string;
}

@ -106,6 +106,33 @@ export class HalResource {
this.halInitializer(this);
}
/**
* Returns the ID and ensures it's a string, null.
* Returns a string when:
* - The embedded ID is actually set
* - The self link is terminated by a number.
*/
public get id():string|null {
if (this.$source.id) {
return this.$source.id.toString();
}
const id = this.idFromLink;
if (id.match(/^\d+$/)) {
return id;
}
return null;
}
public set id(val:string|null) {
this.$source.id = val;
}
public get persisted() {
return this.id && this.id !== 'new';
}
/**
* Create a HalResource from the copied source of the given, other HalResource.
*
@ -156,10 +183,6 @@ export class HalResource {
return null;
}
public getId():string {
return this.id || this.idFromLink;
}
public $load(force = false):Promise<this> {
if (!this.state) {
return this.$loadResource(force);
@ -217,7 +240,7 @@ export class HalResource {
*/
public $embeddableKeys():string[] {
const properties = Object.keys(this.$source);
return _.without(properties, '_links', '_embedded');
return _.without(properties, '_links', '_embedded', 'id');
}
/**

@ -54,7 +54,6 @@ export interface TimelineLabels {
export class QueryResource extends HalResource {
public $embedded:QueryResourceEmbedded;
public id:number;
public results:WorkPackageCollectionResource;
public columns:QueryColumn[];
public groupBy:QueryGroupByResource|undefined;

@ -72,7 +72,6 @@ export class RelationResource extends HalResource {
}
// Properties
public id:number;
public description:string|null;
public name:string;
public type:any;
@ -98,7 +97,7 @@ export class RelationResource extends HalResource {
return {
target: this[target],
targetId: this[target].id,
targetId: this[target].id!,
relationType: target === 'from' ? this.reverseType : this.type,
reverseRelationType: target === 'from' ? this.type : this.reverseType
};

@ -32,7 +32,6 @@ import {InputState} from 'reactivestates';
export class UserResource extends HalResource {
// Properties
public id:number|string;
public login:string;
public firstName:string;
public lastName:string;

@ -136,16 +136,12 @@ export class WorkPackageBaseResource extends HalResource {
readonly attachmentsBackend = true;
public get id():string {
return this.$source.id || this.idFromLink;
}
/**
* Return the ids of all its ancestors, if any
*/
public get ancestorIds():string {
public get ancestorIds():string[] {
const ancestors = (this as any).ancestors;
return ancestors.map((el:WorkPackageResource) => el.id.toString());
return ancestors.map((el:WorkPackageResource) => el.id!);
}
public get isReadonly():boolean {
@ -246,7 +242,7 @@ export class WorkPackageBaseResource extends HalResource {
const promise = Promise.all(_.values(resources));
promise.then(() => {
this.wpCacheService.touch(this.id);
this.wpCacheService.touch(this.id!);
});
return promise;

@ -89,7 +89,7 @@ export class WorkPackagesFullViewComponent extends WorkPackageSingleViewBase {
super.init();
// Set Focused WP
this.wpTableFocus.updateFocus(this.workPackage.id);
this.wpTableFocus.updateFocus(this.workPackage.id!);
this.setWorkPackageScopeProperties(this.workPackage);
this.text.goBack = this.I18n.t('js.button_back');

@ -125,8 +125,9 @@ export class WorkPackagesListComponent extends WorkPackagesViewBase implements O
.catch(() => this.querySaving = false);
}
updateTitle(query:QueryResource) {
if (query.id) {
if (query.persisted) {
this.selectedTitle = query.name;
this.titleEditingEnabled = this.authorisationService.can('query', 'updateImmediately');
} else {
@ -168,7 +169,7 @@ export class WorkPackagesListComponent extends WorkPackagesViewBase implements O
const params = transition.params('to');
let newChecksum = this.wpListService.getCurrentQueryProps(params);
let newId = params.query_id && parseInt(params.query_id);
let newId:string = params.query_id ? params.query_id.toString() : null;
this.wpListChecksumService
.executeIfOutdated(newId,

@ -144,7 +144,6 @@ export abstract class WorkPackagesViewBase implements OnInit, OnDestroy {
if (triggerUpdate) {
this.wpTableRefresh.request(
'Query updated by user',
'update',
{ visible: true, firstPage: firstPage }
);
}

Loading…
Cancel
Save