diff --git a/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts b/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts
index 434bce9255..59ca53ab78 100644
--- a/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts
+++ b/frontend/src/app/features/in-app-notifications/bell/in-app-notification-bell.component.ts
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { InAppNotificationsQuery } from 'core-app/features/in-app-notifications/store/in-app-notifications.query';
+import { InAppNotificationsStore } from 'core-app/features/in-app-notifications/store/in-app-notifications.store';
import { InAppNotificationsService } from 'core-app/features/in-app-notifications/store/in-app-notifications.service';
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
import { merge, timer } from 'rxjs';
@@ -15,12 +16,17 @@ const POLLING_INTERVAL = 10000;
templateUrl: './in-app-notification-bell.component.html',
styleUrls: ['./in-app-notification-bell.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [
+ InAppNotificationsService,
+ InAppNotificationsStore,
+ InAppNotificationsQuery,
+ ],
})
export class InAppNotificationBellComponent {
polling$ = timer(10, POLLING_INTERVAL)
.pipe(
filter(() => this.activeWindow.isActive),
- switchMap(() => this.inAppService.count$()),
+ switchMap(() => this.inAppService.fetchCount()),
);
unreadCount$ = merge(
diff --git a/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.html b/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.html
index a2d0f115b0..abe43f402a 100644
--- a/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.html
+++ b/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.html
@@ -26,9 +26,9 @@
diff --git a/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts b/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts
index db1cb1f8f1..99ab65ed7f 100644
--- a/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts
+++ b/frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts
@@ -7,6 +7,7 @@ import {
} from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { InAppNotificationsQuery } from 'core-app/features/in-app-notifications/store/in-app-notifications.query';
+import { InAppNotificationsStore } from 'core-app/features/in-app-notifications/store/in-app-notifications.store';
import { InAppNotificationsService } from 'core-app/features/in-app-notifications/store/in-app-notifications.service';
import { NOTIFICATIONS_MAX_SIZE } from 'core-app/features/in-app-notifications/store/in-app-notification.model';
import { map } from 'rxjs/operators';
@@ -19,6 +20,11 @@ import { UIRouterGlobals } from '@uirouter/core';
templateUrl: './in-app-notification-center.component.html',
styleUrls: ['./in-app-notification-center.component.sass'],
changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [
+ InAppNotificationsService,
+ InAppNotificationsStore,
+ InAppNotificationsQuery,
+ ],
})
export class InAppNotificationCenterComponent implements OnInit {
activeFacet$ = this.ianQuery.activeFacet$;
@@ -42,6 +48,11 @@ export class InAppNotificationCenterComponent implements OnInit {
map((facet:'unread'|'all') => this.text.no_results[facet] || this.text.no_results.unread),
);
+ totalCountWarning$ = this.ianQuery.notLoaded$.pipe(map((notLoaded:number) => this.I18n.t(
+ 'js.notifications.center.total_count_warning',
+ { newest_count: NOTIFICATIONS_MAX_SIZE, more_count: notLoaded },
+ )));
+
maxSize = NOTIFICATIONS_MAX_SIZE;
facets:string[] = ['unread', 'all'];
@@ -65,20 +76,11 @@ export class InAppNotificationCenterComponent implements OnInit {
readonly ianQuery:InAppNotificationsQuery,
readonly uiRouterGlobals:UIRouterGlobals,
readonly state:StateService,
- ) {
- }
+ ) { }
ngOnInit():void {
- this.ianService.get();
- }
-
- totalCountWarning():string {
- const state = this.ianQuery.getValue();
-
- return this.I18n.t(
- 'js.notifications.center.total_count_warning',
- { newest_count: NOTIFICATIONS_MAX_SIZE, more_count: state.notShowing },
- );
+ this.ianService.setActiveFacet('unread');
+ this.ianService.setActiveFilters([]);
}
openSplitView($event:WorkPackageResource):void {
diff --git a/frontend/src/app/features/in-app-notifications/store/in-app-notifications.query.ts b/frontend/src/app/features/in-app-notifications/store/in-app-notifications.query.ts
index 1d94ac55d1..adaf78acc6 100644
--- a/frontend/src/app/features/in-app-notifications/store/in-app-notifications.query.ts
+++ b/frontend/src/app/features/in-app-notifications/store/in-app-notifications.query.ts
@@ -8,11 +8,16 @@ import {
} from './in-app-notifications.store';
import { InAppNotification } from 'core-app/features/in-app-notifications/store/in-app-notification.model';
-@Injectable({ providedIn: 'root' })
+@Injectable()
export class InAppNotificationsQuery extends QueryEntity {
/** Select the active filter facet */
activeFacet$ = this.select('activeFacet');
+ activeFetchParameters$ = this.select(['activeFacet', 'activeFilters']);
+
+ /** Select the active filter facet */
+ notLoaded$ = this.select('notLoaded');
+
/** Get the faceted items */
faceted$ = this.activeFacet$
.pipe(
@@ -60,7 +65,7 @@ export class InAppNotificationsQuery extends QueryEntity notShowing > 0),
+ map(({ notLoaded }) => notLoaded > 0),
);
constructor(protected store:InAppNotificationsStore) {
diff --git a/frontend/src/app/features/in-app-notifications/store/in-app-notifications.service.ts b/frontend/src/app/features/in-app-notifications/store/in-app-notifications.service.ts
index 7d042087fe..3220ed6797 100644
--- a/frontend/src/app/features/in-app-notifications/store/in-app-notifications.service.ts
+++ b/frontend/src/app/features/in-app-notifications/store/in-app-notifications.service.ts
@@ -1,17 +1,15 @@
-import { EventEmitter, Injectable } from '@angular/core';
+import { Injectable } from '@angular/core';
+import { map, switchMap, tap, take, debounceTime } from 'rxjs/operators';
import { applyTransaction, ID, setLoading } from '@datorama/akita';
-import { Observable } from 'rxjs';
+import { ApiV3ListFilter } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
-import { map, switchMap, tap } from 'rxjs/operators';
import { NotificationsService } from 'core-app/shared/components/notifications/notifications.service';
import { InAppNotificationsQuery } from 'core-app/features/in-app-notifications/store/in-app-notifications.query';
-import { take } from 'rxjs/internal/operators/take';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { InAppNotificationsStore } from './in-app-notifications.store';
import { InAppNotification, NOTIFICATIONS_MAX_SIZE } from './in-app-notification.model';
-import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
-@Injectable({ providedIn: 'root' })
+@Injectable()
export class InAppNotificationsService {
constructor(
private store:InAppNotificationsStore,
@@ -19,43 +17,50 @@ export class InAppNotificationsService {
private apiV3Service:APIV3Service,
private notifications:NotificationsService,
) {
+ this.query.activeFetchParameters$
+ .pipe(debounceTime(0))
+ .subscribe(() => {
+ this.fetchNotifications();
+ this.fetchCount();
+ });
}
- public notificationsOfWpLoaded:EventEmitter> = new EventEmitter>();
-
- get():void {
+ fetchNotifications() {
this.store.setLoading(true);
- const facet = this.query.getValue().activeFacet;
+ const { activeFacet, activeFilters } = this.query.getValue();
- this
+ const call = this
.apiV3Service
.notifications
- .facet(facet, { pageSize: NOTIFICATIONS_MAX_SIZE })
+ .facet(activeFacet, {
+ pageSize: NOTIFICATIONS_MAX_SIZE,
+ filters: activeFilters,
+ });
+
+ call
.pipe(
tap((events) => this.sideLoadInvolvedWorkPackages(events._embedded.elements)),
)
.subscribe(
- (events) => {
- applyTransaction(() => {
- this.store.set(events._embedded.elements);
- this.store.update({ notShowing: events.total - events.count });
- });
- },
- (error) => {
- this.notifications.addError(error);
- },
+ (events) => applyTransaction(() => {
+ this.store.set(events._embedded.elements);
+ this.store.update({ notShowing: events.total - events.count });
+ }),
+ (error) => this.notifications.addError(error),
)
- .add(
- () => this.store.setLoading(false),
- );
+ .add(() => this.store.setLoading(false));
+
+ return call;
}
- count$():Observable {
+ fetchCount() {
+ const { activeFilters } = this.query.getValue();
+
return this
.apiV3Service
.notifications
- .unread({ pageSize: 0 })
+ .unread({ pageSize: 0, filters: activeFilters })
.pipe(
map((events) => events.total),
tap((unreadCount) => {
@@ -72,8 +77,12 @@ export class InAppNotificationsService {
this.store.update((state) => ({ ...state, activeFacet: facet }));
}
- markAllRead():void {
- this.query
+ setActiveFilters(filters:ApiV3ListFilter[]):void {
+ this.store.update((state) => ({ ...state, activeFilters: filters }));
+ }
+
+ markAllRead() {
+ return this.query
.unread$
.pipe(
take(1),
@@ -88,10 +97,10 @@ export class InAppNotificationsService {
});
}
- markAsRead(notifications:InAppNotification[], keep = false):void {
+ markAsRead(notifications:InAppNotification[], keep = false) {
const ids = notifications.map((n) => n.id);
- this
+ return this
.apiV3Service
.notifications
.markRead(ids)
@@ -108,24 +117,6 @@ export class InAppNotificationsService {
});
}
- loadNotificationsOfWorkPackage(workPackageId:string):void {
- this
- .apiV3Service
- .notifications
- .facet(
- 'unread',
- {
- pageSize: NOTIFICATIONS_MAX_SIZE,
- filters: [
- ['resourceId', '=', [workPackageId]],
- ['resourceType', '=', ['WorkPackage']],
- ],
- },
- ).subscribe((notificationCollection) => {
- this.notificationsOfWpLoaded.emit(notificationCollection);
- });
- }
-
private sideLoadInvolvedWorkPackages(elements:InAppNotification[]) {
const wpIds = elements.map((element) => {
const href = element._links.resource?.href;
@@ -137,22 +128,4 @@ export class InAppNotificationsService {
.work_packages
.requireAll(_.compact(wpIds));
}
-
- collapse(notification:InAppNotification):void {
- this.store.update(
- notification.id,
- {
- expanded: false,
- },
- );
- }
-
- expand(notification:InAppNotification):void {
- this.store.update(
- notification.id,
- {
- expanded: true,
- },
- );
- }
}
diff --git a/frontend/src/app/features/in-app-notifications/store/in-app-notifications.store.ts b/frontend/src/app/features/in-app-notifications/store/in-app-notifications.store.ts
index c53b8280da..5a415c207c 100644
--- a/frontend/src/app/features/in-app-notifications/store/in-app-notifications.store.ts
+++ b/frontend/src/app/features/in-app-notifications/store/in-app-notifications.store.ts
@@ -1,26 +1,29 @@
import { Injectable } from '@angular/core';
import { EntityState, EntityStore, StoreConfig } from '@datorama/akita';
import { InAppNotification } from './in-app-notification.model';
+import { ApiV3ListFilter } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
export interface InAppNotificationsState extends EntityState {
/** The entities in the store might not all be unread so we keep separate count */
unreadCount:number;
/** Number of elements not showing after max values loaded */
- notShowing:number;
+ notLoaded:number;
activeFacet:string;
+ activeFilters:ApiV3ListFilter[];
expanded:boolean;
}
export function createInitialState():InAppNotificationsState {
return {
unreadCount: 0,
- notShowing: 0,
+ notLoaded: 0,
activeFacet: 'unread',
+ activeFilters: [],
expanded: false,
};
}
-@Injectable({ providedIn: 'root' })
+@Injectable()
@StoreConfig({ name: 'in-app-notifications' })
export class InAppNotificationsStore extends EntityStore {
constructor() {
diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts
index 1e88addfb9..b4ad7ab6f8 100644
--- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts
+++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-base.controller.ts
@@ -28,7 +28,6 @@
import { ChangeDetectorRef, Directive, OnInit } from '@angular/core';
import { Transition } from '@uirouter/core';
-import { combineLatest } from 'rxjs';
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { ActivityEntryInfo } from 'core-app/features/work-packages/components/wp-single-view-tabs/activity-panel/activity-entry-info';
@@ -95,6 +94,15 @@ export class ActivityPanelBaseController extends UntilDestroyedMixin implements
});
});
+
+ this.ianService.setActiveFacet('unread');
+ this.ianService.setActiveFilters([
+ ['resourceId', '=', [workPackageId]],
+ ['resourceType', '=', ['WorkPackage']],
+ ]);
+ this.ianService.fetchNotifications();
+
+
this.ianService.loadNotificationsOfWorkPackage(this.workPackageId);
this.ianService.notificationsOfWpLoaded.subscribe((notificationCollection) => {
this.notifications = notificationCollection._embedded.elements;
diff --git a/frontend/src/app/features/work-packages/routing/wp-full-view/wp-full-view.component.ts b/frontend/src/app/features/work-packages/routing/wp-full-view/wp-full-view.component.ts
index 88d0f8bd31..e548fa6e23 100644
--- a/frontend/src/app/features/work-packages/routing/wp-full-view/wp-full-view.component.ts
+++ b/frontend/src/app/features/work-packages/routing/wp-full-view/wp-full-view.component.ts
@@ -29,9 +29,12 @@
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
import { StateService } from '@uirouter/core';
import { Component, Injector, OnInit } from '@angular/core';
+import { of } from 'rxjs';
import { WorkPackageViewSelectionService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-selection.service';
import { WorkPackageSingleViewBase } from 'core-app/features/work-packages/routing/wp-view-base/work-package-single-view.base';
-import { of } from 'rxjs';
+import { InAppNotificationsQuery } from 'core-app/features/in-app-notifications/store/in-app-notifications.query';
+import { InAppNotificationsStore } from 'core-app/features/in-app-notifications/store/in-app-notifications.store';
+import { InAppNotificationsService } from 'core-app/features/in-app-notifications/store/in-app-notifications.service';
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service';
import { InAppNotificationsService } from 'core-app/features/in-app-notifications/store/in-app-notifications.service';
@@ -43,6 +46,9 @@ import { InAppNotificationsService } from 'core-app/features/in-app-notification
host: { class: 'work-packages-page--ui-view' },
providers: [
{ provide: HalResourceNotificationService, useExisting: WorkPackageNotificationService },
+ InAppNotificationsService,
+ InAppNotificationsStore,
+ InAppNotificationsQuery,
],
})
export class WorkPackagesFullViewComponent extends WorkPackageSingleViewBase implements OnInit {
diff --git a/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view.component.ts b/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view.component.ts
index aded989e16..d8b7de7695 100644
--- a/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view.component.ts
+++ b/frontend/src/app/features/work-packages/routing/wp-split-view/wp-split-view.component.ts
@@ -30,6 +30,9 @@ import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/c
import { StateService } from '@uirouter/core';
import { WorkPackageViewFocusService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-focus.service';
import { States } from 'core-app/core/states/states.service';
+import { InAppNotificationsQuery } from 'core-app/features/in-app-notifications/store/in-app-notifications.query';
+import { InAppNotificationsStore } from 'core-app/features/in-app-notifications/store/in-app-notifications.store';
+import { InAppNotificationsService } from 'core-app/features/in-app-notifications/store/in-app-notifications.service';
import { FirstRouteService } from 'core-app/core/routing/first-route-service';
import { KeepTabService } from 'core-app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service';
import { WorkPackageViewSelectionService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-selection.service';
@@ -45,6 +48,9 @@ import { InAppNotificationsService } from 'core-app/features/in-app-notification
selector: 'wp-split-view-entry',
providers: [
{ provide: HalResourceNotificationService, useClass: WorkPackageNotificationService },
+ InAppNotificationsService,
+ InAppNotificationsStore,
+ InAppNotificationsQuery,
],
})
export class WorkPackageSplitViewComponent extends WorkPackageSingleViewBase implements OnInit {