diff --git a/frontend/src/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts b/frontend/src/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts index 552b54967f..2ea66d3949 100644 --- a/frontend/src/app/components/modals/wp-destroy-modal/wp-destroy.modal.ts +++ b/frontend/src/app/components/modals/wp-destroy-modal/wp-destroy.modal.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); diff --git a/frontend/src/app/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts b/frontend/src/app/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts index 078b794296..6995bc299c 100644 --- a/frontend/src/app/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts +++ b/frontend/src/app/components/op-context-menu/handlers/op-settings-dropdown-menu.directive.ts @@ -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); diff --git a/frontend/src/app/components/op-context-menu/handlers/op-types-context-menu.directive.ts b/frontend/src/app/components/op-context-menu/handlers/op-types-context-menu.directive.ts index b799269256..c8c2d52a81 100644 --- a/frontend/src/app/components/op-context-menu/handlers/op-types-context-menu.directive.ts +++ b/frontend/src/app/components/op-context-menu/handlers/op-types-context-menu.directive.ts @@ -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; diff --git a/frontend/src/app/components/op-context-menu/handlers/wp-status-dropdown-menu.directive.ts b/frontend/src/app/components/op-context-menu/handlers/wp-status-dropdown-menu.directive.ts index 058e4a1ad8..cb96c4c20a 100644 --- a/frontend/src/app/components/op-context-menu/handlers/wp-status-dropdown-menu.directive.ts +++ b/frontend/src/app/components/op-context-menu/handlers/wp-status-dropdown-menu.directive.ts @@ -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; diff --git a/frontend/src/app/components/states.service.ts b/frontend/src/app/components/states.service.ts index 8585a63f53..32368120e6 100644 --- a/frontend/src/app/components/states.service.ts +++ b/frontend/src/app/components/states.service.ts @@ -57,7 +57,7 @@ export class States extends StatesGroup { state = this.additional[stateName] = multiInput(); } - return state && state.get(resource.id); + return state && state.get(resource.id!); } public add(name:string, state:MultiInputState) { diff --git a/frontend/src/app/components/work-packages/work-package-authorization.service.ts b/frontend/src/app/components/work-packages/work-package-authorization.service.ts index 570209fa9f..6813d3c340 100644 --- a/frontend/src/app/components/work-packages/work-package-authorization.service.ts +++ b/frontend/src/app/components/work-packages/work-package-authorization.service.ts @@ -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!); } } diff --git a/frontend/src/app/components/work-packages/work-package-cache.service.spec.ts b/frontend/src/app/components/work-packages/work-package-cache.service.spec.ts index 32468f7407..1032c79d01 100644 --- a/frontend/src/app/components/work-packages/work-package-cache.service.spec.ts +++ b/frontend/src/app/components/work-packages/work-package-cache.service.spec.ts @@ -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) { diff --git a/frontend/src/app/components/work-packages/work-package-cache.service.ts b/frontend/src/app/components/work-packages/work-package-cache.service.ts index 474284e60d..134199d5b9 100644 --- a/frontend/src/app/components/work-packages/work-package-cache.service.ts +++ b/frontend/src/app/components/work-packages/work-package-cache.service.ts @@ -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 { 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) { diff --git a/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts b/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts index ad156636f4..ec673bed45 100644 --- a/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts +++ b/frontend/src/app/components/work-packages/wp-single-view/wp-single-view.component.ts @@ -148,7 +148,7 @@ export class WorkPackageSingleViewComponent implements OnInit, OnDestroy { .pipe( takeUntil(componentDestroyed(this)), distinctUntilChanged((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)) diff --git a/frontend/src/app/components/work-packages/wp-watcher-button/wp-watcher-button.component.ts b/frontend/src/app/components/work-packages/wp-watcher-button/wp-watcher-button.component.ts index 49d6a3663f..be35e91411 100644 --- a/frontend/src/app/components/work-packages/wp-watcher-button/wp-watcher-button.component.ts +++ b/frontend/src/app/components/work-packages/wp-watcher-button/wp-watcher-button.component.ts @@ -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); }); } diff --git a/frontend/src/app/components/wp-activity/activity-link.component.ts b/frontend/src/app/components/wp-activity/activity-link.component.ts index 105a0c00e7..7f05379e58 100644 --- a/frontend/src/app/components/wp-activity/activity-link.component.ts +++ b/frontend/src/app/components/wp-activity/activity-link.component.ts @@ -7,7 +7,7 @@ import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-r + [uiParams]="{workPackageId: workPackage.id!, '#': activityHtmlId }"> ` }) @@ -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; } }; diff --git a/frontend/src/app/components/wp-activity/revision/revision-activity.component.ts b/frontend/src/app/components/wp-activity/revision/revision-activity.component.ts index 138e90b9fb..802a040aee 100644 --- a/frontend/src/app/components/wp-activity/revision/revision-activity.component.ts +++ b/frontend/src/app/components/wp-activity/revision/revision-activity.component.ts @@ -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; diff --git a/frontend/src/app/components/wp-activity/user/user-activity.component.ts b/frontend/src/app/components/wp-activity/user/user-activity.component.ts index 7e5faae572..1c47ce3e69 100644 --- a/frontend/src/app/components/wp-activity/user/user-activity.component.ts +++ b/frontend/src/app/components/wp-activity/user/user-activity.component.ts @@ -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; diff --git a/frontend/src/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts b/frontend/src/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts index 6dcf6be47f..ce58cc0693 100644 --- a/frontend/src/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts +++ b/frontend/src/app/components/wp-buttons/wp-status-button/wp-status-button.component.ts @@ -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 { diff --git a/frontend/src/app/components/wp-by-version-graph/wp-by-version-graph.component.ts b/frontend/src/app/components/wp-by-version-graph/wp-by-version-graph.component.ts index 8a6a17aee8..ce96544152 100644 --- a/frontend/src/app/components/wp-by-version-graph/wp-by-version-graph.component.ts +++ b/frontend/src/app/components/wp-by-version-graph/wp-by-version-graph.component.ts @@ -52,7 +52,7 @@ export class WorkPackageByVersionGraphComponent implements OnInit { } if (this.currentGraph) { - this.wpTableRefresh.request('Refresh graph', 'update'); + this.wpTableRefresh.request('Refresh graph'); } } diff --git a/frontend/src/app/components/wp-card-view/card-reorder-query.service.ts b/frontend/src/app/components/wp-card-view/card-reorder-query.service.ts deleted file mode 100644 index f2902e6e31..0000000000 --- a/frontend/src/app/components/wp-card-view/card-reorder-query.service.ts +++ /dev/null @@ -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()), []); - } -} diff --git a/frontend/src/app/components/wp-card-view/wp-card-view.component.html b/frontend/src/app/components/wp-card-view/wp-card-view.component.html index a584dcefe1..ea6cf9ef34 100644 --- a/frontend/src/app/components/wp-card-view/wp-card-view.component.html +++ b/frontend/src/app/components/wp-card-view/wp-card-view.component.html @@ -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 }">
diff --git a/frontend/src/app/components/wp-card-view/wp-card-view.component.sass b/frontend/src/app/components/wp-card-view/wp-card-view.component.sass index 24ccd4955c..8cdce87501 100644 --- a/frontend/src/app/components/wp-card-view/wp-card-view.component.sass +++ b/frontend/src/app/components/wp-card-view/wp-card-view.component.sass @@ -11,6 +11,7 @@ .wp-card + user-select: none width: 100% border: 1px solid var(--widget-box-block-border-color) border-radius: 5px diff --git a/frontend/src/app/components/wp-card-view/wp-card-view.component.ts b/frontend/src/app/components/wp-card-view/wp-card-view.component.ts index af74283471..f2b85fc9d4 100644 --- a/frontend/src/app/components/wp-card-view/wp-card-view.component.ts +++ b/frontend/src/app/components/wp-card-view/wp-card-view.component.ts @@ -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(); + + // 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 { 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!); } } diff --git a/frontend/src/app/components/wp-copy/wp-copy.controller.ts b/frontend/src/app/components/wp-copy/wp-copy.controller.ts index dd0edc4519..f843a370f2 100644 --- a/frontend/src/app/components/wp-copy/wp-copy.controller.ts +++ b/frontend/src/app/components/wp-copy/wp-copy.controller.ts @@ -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); } }); } diff --git a/frontend/src/app/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts b/frontend/src/app/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts index 88b6c71e35..3ddde475f3 100644 --- a/frontend/src/app/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts +++ b/frontend/src/app/components/wp-custom-actions/wp-custom-actions/wp-custom-action.component.ts @@ -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); diff --git a/frontend/src/app/components/wp-edit-form/work-package-changeset.ts b/frontend/src/app/components/wp-edit-form/work-package-changeset.ts index 9c67eb4a8c..1ed88d48b3 100644 --- a/frontend/src/app/components/wp-edit-form/work-package-changeset.ts +++ b/frontend/src/app/components/wp-edit-form/work-package-changeset.ts @@ -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); } } diff --git a/frontend/src/app/components/wp-edit-form/work-package-edit-form.ts b/frontend/src/app/components/wp-edit-form/work-package-edit-form.ts index da6377081a..fe5338b67e 100644 --- a/frontend/src/app/components/wp-edit-form/work-package-edit-form.ts +++ b/frontend/src/app/components/wp-edit-form/work-package-edit-form.ts @@ -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) => { diff --git a/frontend/src/app/components/wp-edit-form/work-package-editing-service.ts b/frontend/src/app/components/wp-edit-form/work-package-editing-service.ts index ecee452f49..d027c662c3 100644 --- a/frontend/src/app/components/wp-edit-form/work-package-editing-service.ts +++ b/frontend/src/app/components/wp-edit-form/work-package-editing-service.ts @@ -66,7 +66,7 @@ export class WorkPackageEditingService extends StateCacheService 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); }); } diff --git a/frontend/src/app/components/wp-edit/wp-notification.service.ts b/frontend/src/app/components/wp-edit/wp-notification.service.ts index b9f81ab4de..11aee46605 100644 --- a/frontend/src/app/components/wp-edit/wp-notification.service.ts +++ b/frontend/src/app/components/wp-edit/wp-notification.service.ts @@ -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) } }); diff --git a/frontend/src/app/components/wp-fast-table/builders/highlighting/row-highlight-render-pass.ts b/frontend/src/app/components/wp-fast-table/builders/highlighting/row-highlight-render-pass.ts index 5c3d223a13..803bab8e8a 100644 --- a/frontend/src/app/components/wp-fast-table/builders/highlighting/row-highlight-render-pass.ts +++ b/frontend/src/app/components/wp-fast-table/builders/highlighting/row-highlight-render-pass.ts @@ -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)); diff --git a/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts b/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts index 6c12dc90ac..7d12ebc12d 100644 --- a/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts +++ b/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts @@ -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); diff --git a/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/single-hierarchy-row-builder.ts b/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/single-hierarchy-row-builder.ts index 1e16be91e8..f9f336b014 100644 --- a/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/single-hierarchy-row-builder.ts +++ b/frontend/src/app/components/wp-fast-table/builders/modes/hierarchy/single-hierarchy-row-builder.ts @@ -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; diff --git a/frontend/src/app/components/wp-fast-table/builders/relation-cell-builder.ts b/frontend/src/app/components/wp-fast-table/builders/relation-cell-builder.ts index f91fecd2fa..48ba907195 100644 --- a/frontend/src/app/components/wp-fast-table/builders/relation-cell-builder.ts +++ b/frontend/src/app/components/wp-fast-table/builders/relation-cell-builder.ts @@ -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); diff --git a/frontend/src/app/components/wp-fast-table/builders/relations/relation-row-builder.ts b/frontend/src/app/components/wp-fast-table/builders/relations/relation-row-builder.ts index 92475ac1bd..113b19caab 100644 --- a/frontend/src/app/components/wp-fast-table/builders/relations/relation-row-builder.ts +++ b/frontend/src/app/components/wp-fast-table/builders/relations/relation-row-builder.ts @@ -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 " 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; } diff --git a/frontend/src/app/components/wp-fast-table/builders/relations/relations-render-pass.ts b/frontend/src/app/components/wp-fast-table/builders/relations/relations-render-pass.ts index 1c4a8b7a29..32f04fe388 100644 --- a/frontend/src/app/components/wp-fast-table/builders/relations/relations-render-pass.ts +++ b/frontend/src/app/components/wp-fast-table/builders/relations/relations-render-pass.ts @@ -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; diff --git a/frontend/src/app/components/wp-fast-table/builders/rows/single-row-builder.ts b/frontend/src/app/components/wp-fast-table/builders/rows/single-row-builder.ts index 244eae5abd..f2e98b2c62 100644 --- a/frontend/src/app/components/wp-fast-table/builders/rows/single-row-builder.ts +++ b/frontend/src/app/components/wp-fast-table/builders/rows/single-row-builder.ts @@ -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); } diff --git a/frontend/src/app/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts b/frontend/src/app/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts index 1a044b3a5a..5896f03613 100644 --- a/frontend/src/app/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts +++ b/frontend/src/app/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts @@ -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!), []); + } } diff --git a/frontend/src/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts b/frontend/src/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts index 3a4fd760d4..7873eec1cc 100644 --- a/frontend/src/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts +++ b/frontend/src/app/components/wp-fast-table/helpers/wp-table-hierarchy-helpers.ts @@ -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; }); } diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-additional-elements.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-additional-elements.service.ts index d0a5c1fa14..02203ee052 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-additional-elements.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-additional-elements.service.ts @@ -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)); diff --git a/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts b/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts index 813eb5022e..12fb7220de 100644 --- a/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts +++ b/frontend/src/app/components/wp-fast-table/state/wp-table-relation-columns.service.ts @@ -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; } diff --git a/frontend/src/app/components/wp-fast-table/wp-fast-table.ts b/frontend/src/app/components/wp-fast-table/wp-fast-table.ts index 3427e536ad..03abe8d99a 100644 --- a/frontend/src/app/components/wp-fast-table/wp-fast-table.ts +++ b/frontend/src/app/components/wp-fast-table/wp-fast-table.ts @@ -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] = {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); diff --git a/frontend/src/app/components/wp-fast-table/wp-table-editing.ts b/frontend/src/app/components/wp-fast-table/wp-table-editing.ts index 651717bc3e..5fa47ae42b 100644 --- a/frontend/src/app/components/wp-fast-table/wp-table-editing.ts +++ b/frontend/src/app/components/wp-fast-table/wp-table-editing.ts @@ -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; diff --git a/frontend/src/app/components/wp-inline-create/inline-create-row-builder.ts b/frontend/src/app/components/wp-inline-create/inline-create-row-builder.ts index cdb9539bba..d5ee151ad1 100644 --- a/frontend/src/app/components/wp-inline-create/inline-create-row-builder.ts +++ b/frontend/src/app/components/wp-inline-create/inline-create-row-builder.ts @@ -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', diff --git a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts index 6d188e4f96..94c8ec152f 100644 --- a/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts +++ b/frontend/src/app/components/wp-inline-create/wp-inline-create.component.ts @@ -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(); diff --git a/frontend/src/app/components/wp-list/wp-list-checksum.service.ts b/frontend/src/app/components/wp-list/wp-list-checksum.service.ts index 08bc25973f..e3184187ae 100644 --- a/frontend/src/app/components/wp-list/wp-list-checksum.service.ts +++ b/frontend/src/app/components/wp-list/wp-list-checksum.service.ts @@ -38,9 +38,9 @@ export class WorkPackagesListChecksumService { protected $state:StateService) { } - public id:number | null; - public checksum:string | null; - public visibleChecksum:string | null; + public id:string|null; + public checksum:string|null; + public visibleChecksum:string|null; public updateIfDifferent(query:QueryResource, pagination:WorkPackageTablePagination) { @@ -75,7 +75,7 @@ export class WorkPackagesListChecksumService { this.set(query.id, newQueryChecksum); this.maintainUrlQueryState(query.id, null); -} + } public isQueryOutdated(query:QueryResource, pagination:WorkPackageTablePagination) { @@ -84,8 +84,8 @@ export class WorkPackagesListChecksumService { return this.isOutdated(query.id, newQueryChecksum); } - public executeIfOutdated(newId:number, - newChecksum:string | null, + public executeIfOutdated(newId:string, + newChecksum:string|null, callback:Function) { if (this.isUninitialized() || this.isOutdated(newId, newChecksum)) { this.set(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,9 +143,9 @@ 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 } }); + this.$state.go('.', {query_props: checksum, query_id: id}, {custom: {notify: false}}); } } diff --git a/frontend/src/app/components/wp-list/wp-list.service.ts b/frontend/src/app/components/wp-list/wp-list.service.ts index d0b799b2c1..b396e5d3c7 100644 --- a/frontend/src/app/components/wp-list/wp-list.service.ts +++ b/frontend/src/app/components/wp-list/wp-list.service.ts @@ -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 { + private streamQueryRequest(queryParams:{ query_id?:string, query_props?:string }, projectIdentifier ?:string):Observable { 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 { + public fromQueryParams(queryParams:{ query_id?:string, query_props?:string }, projectIdentifier ?:string):Observable { 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):Promise { 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 { + private handleQueryLoadingError(error:ErrorResource, queryProps:any, queryId?:string, projectIdentifier?:string|null):Promise { this.NotificationsService.addError(this.I18n.t('js.work_packages.faulty_query.description'), error.message); return new Promise((resolve, reject) => { diff --git a/frontend/src/app/components/wp-list/wp-states-initialization.service.ts b/frontend/src/app/components/wp-list/wp-states-initialization.service.ts index b0e2dc1944..5488b0056f 100644 --- a/frontend/src/app/components/wp-list/wp-states-initialization.service.ts +++ b/frontend/src/app/components/wp-list/wp-states-initialization.service.ts @@ -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); diff --git a/frontend/src/app/components/wp-query/url-params-helper.ts b/frontend/src/app/components/wp-query/url-params-helper.ts index 62ba28dd82..0fc8844b0c 100644 --- a/frontend/src/app/components/wp-query/url-params-helper.ts +++ b/frontend/src/app/components/wp-query/url-params-helper.ts @@ -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(); } diff --git a/frontend/src/app/components/wp-relations/embedded/children/wp-children-query.component.ts b/frontend/src/app/components/wp-relations/embedded/children/wp-children-query.component.ts index 7bf7a7e798..bdca4d63f4 100644 --- a/frontend/src/app/components/wp-relations/embedded/children/wp-children-query.component.ts +++ b/frontend/src/app/components/wp-relations/embedded/children/wp-children-query.component.ts @@ -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) ) diff --git a/frontend/src/app/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts b/frontend/src/app/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts index d4c7e5bb0f..88b332d951 100644 --- a/frontend/src/app/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts +++ b/frontend/src/app/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts @@ -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(); diff --git a/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts b/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts index cc61641580..4fc6dd3ec9 100644 --- a/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts +++ b/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-inline-create.service.ts @@ -58,7 +58,7 @@ export class WpRelationInlineCreateService extends WorkPackageInlineCreateServic * Add a new relation of the above type */ public add(from:WorkPackageResource, toId:string):Promise { - return this.wpRelations.addCommonRelation(toId, this.relationType, from.id); + return this.wpRelations.addCommonRelation(toId, this.relationType, from.id!); } /** diff --git a/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-query.component.ts b/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-query.component.ts index 1cf2f0afae..8c21e68f38 100644 --- a/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-query.component.ts +++ b/frontend/src/app/components/wp-relations/embedded/relations/wp-relation-query.component.ts @@ -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)); } diff --git a/frontend/src/app/components/wp-relations/embedded/wp-relation-query.base.ts b/frontend/src/app/components/wp-relations/embedded/wp-relation-query.base.ts index 66afbfeb99..1b632dc924 100644 --- a/frontend/src/app/components/wp-relations/embedded/wp-relation-query.base.ts +++ b/frontend/src/app/components/wp-relations/embedded/wp-relation-query.base.ts @@ -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!); } }); diff --git a/frontend/src/app/components/wp-relations/wp-relation-row/wp-relation-row.component.ts b/frontend/src/app/components/wp-relations/wp-relation-row/wp-relation-row.component.ts index 396f9cfcca..ac9beb6cee 100644 --- a/frontend/src/app/components/wp-relations/wp-relation-row/wp-relation-row.component.ts +++ b/frontend/src/app/components/wp-relations/wp-relation-row/wp-relation-row.component.ts @@ -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); diff --git a/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts b/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts index 5afe13f484..1ab1e4efc2 100644 --- a/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts +++ b/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.upgraded.component.ts @@ -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 }) diff --git a/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-create.component.ts b/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-create.component.ts index 1be8a69cfc..0f1cfc4ff6 100644 --- a/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-create.component.ts +++ b/frontend/src/app/components/wp-relations/wp-relations-create/wp-relations-create.component.ts @@ -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(); }) diff --git a/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts b/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts index 892c3ee5c1..a8c1303284 100644 --- a/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts +++ b/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts @@ -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)) ) diff --git a/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts b/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts index 0665baa889..6f61987315 100644 --- a/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts +++ b/frontend/src/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service.ts @@ -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) => { diff --git a/frontend/src/app/components/wp-relations/wp-relations.component.ts b/frontend/src/app/components/wp-relations/wp-relations.component.ts index 5b438c6be9..5f255c067b 100644 --- a/frontend/src/app/components/wp-relations/wp-relations.component.ts +++ b/frontend/src/app/components/wp-relations/wp-relations.component.ts @@ -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; }); diff --git a/frontend/src/app/components/wp-relations/wp-relations.service.ts b/frontend/src/app/components/wp-relations/wp-relations.service.ts index 45971bd0d3..3797c18d89 100644 --- a/frontend/src/app/components/wp-relations/wp-relations.service.ts +++ b/frontend/src/app/components/wp-relations/wp-relations.service.ts @@ -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 { 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 { 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 r.id); + const relationsToInsert = _.keyBy(relations, r => r.id!); state.putValue(relationsToInsert, 'Overriding relations state.'); } diff --git a/frontend/src/app/components/wp-single-view-tabs/activity-panel/activity-on-overview.component.ts b/frontend/src/app/components/wp-single-view-tabs/activity-panel/activity-on-overview.component.ts index e44741a2d6..6251b0aa37 100644 --- a/frontend/src/app/components/wp-single-view-tabs/activity-panel/activity-on-overview.component.ts +++ b/frontend/src/app/components/wp-single-view-tabs/activity-panel/activity-on-overview.component.ts @@ -53,7 +53,7 @@ export class NewestActivityOnOverviewComponent extends ActivityPanelBaseControll } ngOnInit() { - this.workPackageId = this.workPackage.id; + this.workPackageId = this.workPackage.id!; super.ngOnInit(); } diff --git a/frontend/src/app/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts b/frontend/src/app/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts index 5b38edd89e..808e1eb5aa 100644 --- a/frontend/src/app/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts +++ b/frontend/src/app/components/wp-single-view-tabs/relations-tab/relations-tab.component.ts @@ -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; }); } diff --git a/frontend/src/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts b/frontend/src/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts index e25927b594..3930f15aef 100644 --- a/frontend/src/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts +++ b/frontend/src/app/components/wp-single-view-tabs/watchers-tab/watchers-tab.component.ts @@ -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)); } diff --git a/frontend/src/app/components/wp-single-view-tabs/wp-linked-resource-cache.service.ts b/frontend/src/app/components/wp-single-view-tabs/wp-linked-resource-cache.service.ts index ccee1f95d8..5213df640b 100644 --- a/frontend/src/app/components/wp-single-view-tabs/wp-linked-resource-cache.service.ts +++ b/frontend/src/app/components/wp-single-view-tabs/wp-linked-resource-cache.service.ts @@ -51,7 +51,7 @@ export abstract class WorkPackageLinkedResourceCache { * @returns {Promise} */ public require(workPackage:WorkPackageResource, force:boolean = false):Promise { - 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 { .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(); } } diff --git a/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts b/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts index d6a13f9cdf..b0d9d4e061 100644 --- a/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts +++ b/frontend/src/app/components/wp-table/embedded/wp-embedded-table.component.ts @@ -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(() => { diff --git a/frontend/src/app/components/wp-table/table-actions/actions/details-table-action.ts b/frontend/src/app/components/wp-table/table-actions/actions/details-table-action.ts index 724a69992f..cb889183b5 100644 --- a/frontend/src/app/components/wp-table/table-actions/actions/details-table-action.ts +++ b/frontend/src/app/components/wp-table/table-actions/actions/details-table-action.ts @@ -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, '' ); diff --git a/frontend/src/app/components/wp-table/table-actions/actions/unlink-table-action.ts b/frontend/src/app/components/wp-table/table-actions/actions/unlink-table-action.ts index 2f09f2e45d..bf7c779707 100644 --- a/frontend/src/app/components/wp-table/table-actions/actions/unlink-table-action.ts +++ b/frontend/src/app/components/wp-table/table-actions/actions/unlink-table-action.ts @@ -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(); diff --git a/frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts b/frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts index 36c5e4e051..19c7f43ed0 100644 --- a/frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts +++ b/frontend/src/app/components/wp-table/timeline/cells/timeline-cell-renderer.ts @@ -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) { diff --git a/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts b/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts index ed9e59f23d..ce46e2308c 100644 --- a/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts +++ b/frontend/src/app/components/wp-table/timeline/container/wp-timeline-container.directive.ts @@ -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)); }); } diff --git a/frontend/src/app/components/wp-table/wp-table-refresh-request.service.ts b/frontend/src/app/components/wp-table/wp-table-refresh-request.service.ts index 56a4ed0359..a3700ff8a3 100644 --- a/frontend/src/app/components/wp-table/wp-table-refresh-request.service.ts +++ b/frontend/src/app/components/wp-table/wp-table-refresh-request.service.ts @@ -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 = {}) { - let req = { visible: false, firstPage: false, ...request, origin: origin }; + public request(reason:string, request:Partial = {}) { + let req = { visible: false, firstPage: false, ...request }; this.state.putValue(req, reason); } diff --git a/frontend/src/app/modules/boards/board/board-actions/status-action.service.ts b/frontend/src/app/modules/boards/board/board-actions/status-action.service.ts index c9dd2dbcf5..dbf3b81fde 100644 --- a/frontend/src/app/modules/boards/board/board-actions/status-action.service.ts +++ b/frontend/src/app/modules/boards/board/board-actions/status-action.service.ts @@ -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]); diff --git a/frontend/src/app/modules/boards/board/board-cache.service.ts b/frontend/src/app/modules/boards/board/board-cache.service.ts index 40d7bc2b91..2aa49769fe 100644 --- a/frontend/src/app/modules/boards/board/board-cache.service.ts +++ b/frontend/src/app/modules/boards/board/board-cache.service.ts @@ -35,6 +35,6 @@ export class BoardCacheService extends StateCacheService { } update(board:Board) { - this.updateValue(board.id, board); + this.updateValue(board.id!, board); } } diff --git a/frontend/src/app/modules/boards/board/board-list/board-list.component.html b/frontend/src/app/modules/boards/board/board-list/board-list.component.html index 90207ac77e..2029fe10b7 100644 --- a/frontend/src/app/modules/boards/board/board-list/board-list.component.html +++ b/frontend/src/app/modules/boards/board/board-list/board-list.component.html @@ -38,10 +38,8 @@
-
diff --git a/frontend/src/app/modules/boards/board/board-list/board-list.component.ts b/frontend/src/app/modules/boards/board/board-list/board-list.component.ts index d5d748234a..96995e94bd 100644 --- a/frontend/src/app/modules/boards/board/board-list/board-list.component.ts +++ b/frontend/src/app/modules/boards/board/board-list/board-list.component.ts @@ -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); }); } diff --git a/frontend/src/app/modules/boards/board/board.service.ts b/frontend/src/app/modules/boards/board/board.service.ts index d02bacf45b..10477bc32f 100644 --- a/frontend/src/app/modules/boards/board/board.service.ts +++ b/frontend/src/app/modules/boards/board/board.service.ts @@ -100,7 +100,7 @@ export class BoardService { public delete(board:Board):Promise { return this.boardDm .delete(board) - .then(() => this.boardCache.clearSome(board.id)); + .then(() => this.boardCache.clearSome(board.id!)); } /** diff --git a/frontend/src/app/modules/boards/board/inline-add/board-inline-add-autocompleter.component.ts b/frontend/src/app/modules/boards/board/inline-add/board-inline-add-autocompleter.component.ts index 31b47bef7a..3911a42d42 100644 --- a/frontend/src/app/modules/boards/board/inline-add/board-inline-add-autocompleter.component.ts +++ b/frontend/src/app/modules/boards/board/inline-add/board-inline-add-autocompleter.component.ts @@ -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 diff --git a/frontend/src/app/modules/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts b/frontend/src/app/modules/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts index 125e365d1b..adacb6a596 100644 --- a/frontend/src/app/modules/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts +++ b/frontend/src/app/modules/boards/board/toolbar-menu/boards-toolbar-menu.directive.ts @@ -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 }}); }); } diff --git a/frontend/src/app/modules/boards/drag-and-drop/drag-and-drop.service.ts b/frontend/src/app/modules/boards/drag-and-drop/drag-and-drop.service.ts index 74f17d774d..8d382376c2 100644 --- a/frontend/src/app/modules/boards/drag-and-drop/drag-and-drop.service.ts +++ b/frontend/src/app/modules/boards/drag-and-drop/drag-and-drop.service.ts @@ -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; }); } diff --git a/frontend/src/app/modules/boards/drag-and-drop/reorder-query.service.ts b/frontend/src/app/modules/boards/drag-and-drop/reorder-query.service.ts index f2267d98fb..6020decec4 100644 --- a/frontend/src/app/modules/boards/drag-and-drop/reorder-query.service.ts +++ b/frontend/src/app/modules/boards/drag-and-drop/reorder-query.service.ts @@ -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 { - 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 { - 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 { + public saveOrderInQuery(query:QueryResource|undefined, orderedIds:string[]):Observable { 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"); } } } diff --git a/frontend/src/app/modules/boards/index-page/boards-index-page.component.ts b/frontend/src/app/modules/boards/index-page/boards-index-page.component.ts index 805bf30ce9..d4addf64fb 100644 --- a/frontend/src/app/modules/boards/index-page/boards-index-page.component.ts +++ b/frontend/src/app/modules/boards/index-page/boards-index-page.component.ts @@ -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)); diff --git a/frontend/src/app/modules/boards/openproject-boards.module.ts b/frontend/src/app/modules/boards/openproject-boards.module.ts index fa06b71cdc..03ac7315fc 100644 --- a/frontend/src/app/modules/boards/openproject-boards.module.ts +++ b/frontend/src/app/modules/boards/openproject-boards.module.ts @@ -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, diff --git a/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts b/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts index 01aec556e9..a11e596624 100644 --- a/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts +++ b/frontend/src/app/modules/calendar/wp-calendar/wp-calendar.component.ts @@ -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 }; }); diff --git a/frontend/src/app/modules/fields/display/field-types/wp-display-highlighted-resource-field.module.ts b/frontend/src/app/modules/fields/display/field-types/wp-display-highlighted-resource-field.module.ts index 2da3a414c9..91dd6cdd57 100644 --- a/frontend/src/app/modules/fields/display/field-types/wp-display-highlighted-resource-field.module.ts +++ b/frontend/src/app/modules/fields/display/field-types/wp-display-highlighted-resource-field.module.ts @@ -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); } } diff --git a/frontend/src/app/modules/fields/edit/edit-field.component.ts b/frontend/src/app/modules/fields/edit/edit-field.component.ts index 3d43499574..0736920644 100644 --- a/frontend/src/app/modules/fields/edit/edit-field.component.ts +++ b/frontend/src/app/modules/fields/edit/edit-field.component.ts @@ -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) diff --git a/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts b/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts index f6ae90c325..a5b3d12de2 100644 --- a/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts +++ b/frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.component.ts @@ -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; } } diff --git a/frontend/src/app/modules/global_search/global-search-input.component.ts b/frontend/src/app/modules/global_search/global-search-input.component.ts index fcfb183127..83aed64039 100644 --- a/frontend/src/app/modules/global_search/global-search-input.component.ts +++ b/frontend/src/app/modules/global_search/global-search-input.component.ts @@ -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, diff --git a/frontend/src/app/modules/grids/widgets/time-entries-current-user/time-entries-current-user.component.ts b/frontend/src/app/modules/grids/widgets/time-entries-current-user/time-entries-current-user.component.ts index 71673ef6d4..d2be9e94fb 100644 --- a/frontend/src/app/modules/grids/widgets/time-entries-current-user/time-entries-current-user.component.ts +++ b/frontend/src/app/modules/grids/widgets/time-entries-current-user/time-entries-current-user.component.ts @@ -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) { diff --git a/frontend/src/app/modules/hal/dm-services/query-dm.service.ts b/frontend/src/app/modules/hal/dm-services/query-dm.service.ts index 1b6aacd151..c7e2dee9f0 100644 --- a/frontend/src/app/modules/hal/dm-services/query-dm.service.ts +++ b/frontend/src/app/modules/hal/dm-services/query-dm.service.ts @@ -59,7 +59,7 @@ export class QueryDmService { * @param queryId * @param projectIdentifier */ - public stream(queryData:Object, queryId?:number, projectIdentifier?:string):Observable { + public stream(queryData:Object, queryId?:string, projectIdentifier?:string|null):Observable { let path:string; if (queryId) { @@ -72,16 +72,16 @@ export class QueryDmService { .get(path, queryData); } - public find(queryData:Object, queryId?:number, projectIdentifier?:string):Promise { + public find(queryData:Object, queryId?:string, projectIdentifier?:string|null):Promise { return this.stream(queryData, queryId, projectIdentifier).toPromise(); } - public findDefault(queryData:Object, projectIdentifier?:string):Promise { + public findDefault(queryData:Object, projectIdentifier?:string|null):Promise { return this.find(queryData, undefined, projectIdentifier); } public reload(query:QueryResource, pagination:PaginationObject):Promise { - 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(path, pagination) @@ -123,16 +123,15 @@ export class QueryDmService { .toPromise(); } - public update(query:QueryResource, form:QueryFormResource) { + public update(query:QueryResource, form:QueryFormResource):Observable { 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 { let path:string = this.pathHelper.api.v3.queries.id(id).toString(); return this.halResourceService - .patch(path, payload) - .toPromise(); + .patch(path, payload); } public create(query:QueryResource, form:QueryFormResource):Promise { diff --git a/frontend/src/app/modules/hal/dm-services/query-form-dm.service.ts b/frontend/src/app/modules/hal/dm-services/query-form-dm.service.ts index a82e5c33f9..7dc3f7c6a2 100644 --- a/frontend/src/app/modules/hal/dm-services/query-form-dm.service.ts +++ b/frontend/src/app/modules/hal/dm-services/query-form-dm.service.ts @@ -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 { + public loadWithParams(params:{}, queryId:string|undefined, projectIdentifier:string|undefined|null, payload:any = {}):Promise { // We need a valid payload so that we // can check whether form saving is possible. // The query needs a name to be valid. diff --git a/frontend/src/app/modules/hal/resources/custom-action-resource.ts b/frontend/src/app/modules/hal/resources/custom-action-resource.ts index ecdba29792..f96e23007d 100644 --- a/frontend/src/app/modules/hal/resources/custom-action-resource.ts +++ b/frontend/src/app/modules/hal/resources/custom-action-resource.ts @@ -35,7 +35,6 @@ export interface CustomActionResourceLinks { } export class CustomActionResource extends HalResource { - public id:number; public name:string; public description:string; } diff --git a/frontend/src/app/modules/hal/resources/hal-resource.ts b/frontend/src/app/modules/hal/resources/hal-resource.ts index 40aed51041..0ef999fddf 100644 --- a/frontend/src/app/modules/hal/resources/hal-resource.ts +++ b/frontend/src/app/modules/hal/resources/hal-resource.ts @@ -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 { 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'); } /** diff --git a/frontend/src/app/modules/hal/resources/query-resource.ts b/frontend/src/app/modules/hal/resources/query-resource.ts index 8a32ebfc49..b2888c1ee2 100644 --- a/frontend/src/app/modules/hal/resources/query-resource.ts +++ b/frontend/src/app/modules/hal/resources/query-resource.ts @@ -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; diff --git a/frontend/src/app/modules/hal/resources/relation-resource.ts b/frontend/src/app/modules/hal/resources/relation-resource.ts index d1bc1fb888..170c74d203 100644 --- a/frontend/src/app/modules/hal/resources/relation-resource.ts +++ b/frontend/src/app/modules/hal/resources/relation-resource.ts @@ -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 }; diff --git a/frontend/src/app/modules/hal/resources/user-resource.ts b/frontend/src/app/modules/hal/resources/user-resource.ts index 23a696635f..1c25fe4db8 100644 --- a/frontend/src/app/modules/hal/resources/user-resource.ts +++ b/frontend/src/app/modules/hal/resources/user-resource.ts @@ -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; diff --git a/frontend/src/app/modules/hal/resources/work-package-resource.ts b/frontend/src/app/modules/hal/resources/work-package-resource.ts index 71563abd36..4b7af3729e 100644 --- a/frontend/src/app/modules/hal/resources/work-package-resource.ts +++ b/frontend/src/app/modules/hal/resources/work-package-resource.ts @@ -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; diff --git a/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts b/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts index 92186b7aa6..585af10f39 100644 --- a/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts +++ b/frontend/src/app/modules/work_packages/routing/wp-full-view/wp-full-view.component.ts @@ -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'); diff --git a/frontend/src/app/modules/work_packages/routing/wp-list/wp-list.component.ts b/frontend/src/app/modules/work_packages/routing/wp-list/wp-list.component.ts index f13515f063..bd38efe839 100644 --- a/frontend/src/app/modules/work_packages/routing/wp-list/wp-list.component.ts +++ b/frontend/src/app/modules/work_packages/routing/wp-list/wp-list.component.ts @@ -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, diff --git a/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts b/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts index b69949cf28..0106f3fd1c 100644 --- a/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts +++ b/frontend/src/app/modules/work_packages/routing/wp-view-base/work-packages-view.base.ts @@ -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 } ); }