Start adding projects store

pull/9581/head
Benjamin Bädorf 3 years ago
parent 8020615ecb
commit a6170d7c6c
No known key found for this signature in database
GPG Key ID: 069CA2D117AB5CCF
  1. 4
      frontend/src/app/core/state/openproject-state.module.ts
  2. 20
      frontend/src/app/core/state/projects/project.model.ts
  3. 5
      frontend/src/app/core/state/projects/projects.query.ts
  4. 88
      frontend/src/app/core/state/projects/projects.service.ts
  5. 13
      frontend/src/app/core/state/projects/projects.store.ts
  6. 4
      frontend/src/app/features/in-app-notifications/center/in-app-notification-center.component.ts
  7. 7
      frontend/src/app/features/in-app-notifications/center/menu/state/ian-menu.query.ts
  8. 29
      frontend/src/app/features/in-app-notifications/center/menu/state/ian-menu.service.ts
  9. 2
      frontend/src/app/features/in-app-notifications/center/menu/state/ian-menu.store.ts
  10. 16
      frontend/src/app/features/in-app-notifications/center/state/ian-center.query.ts
  11. 36
      frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts
  12. 3
      frontend/src/app/features/in-app-notifications/center/state/ian-center.store.ts

@ -29,11 +29,13 @@
import { import {
NgModule, NgModule,
} from '@angular/core'; } from '@angular/core';
import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service'; import { InAppNotificationsResourceService } from './in-app-notifications/in-app-notifications.service';
import { ProjectsResourceService } from './projects/projects.service';
@NgModule({ @NgModule({
providers: [ providers: [
InAppNotificationsResourceService, InAppNotificationsResourceService,
ProjectsResourceService,
], ],
}) })
export class OpenProjectStateModule { export class OpenProjectStateModule {

@ -0,0 +1,20 @@
import { ID } from '@datorama/akita';
export interface HalResourceLink {
href:string;
title:string;
}
export const PROJECTS_MAX_SIZE = 100;
export interface Project {
id:ID;
createdAt:string;
updatedAt:string;
_links:{
actor?:HalResourceLink,
project?:HalResourceLink,
resource?:HalResourceLink,
activity?:HalResourceLink,
};
}

@ -0,0 +1,5 @@
import { QueryEntity } from '@datorama/akita';
import { ProjectsState } from './projects.store';
export class ProjectsQuery extends QueryEntity<ProjectsState> {
}

@ -0,0 +1,88 @@
import { Injectable } from '@angular/core';
import {
catchError,
tap,
} from 'rxjs/operators';
import { Observable } from 'rxjs';
import {
applyTransaction,
ID,
} from '@datorama/akita';
import { HttpClient } from '@angular/common/http';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { NotificationsService } from 'core-app/shared/components/notifications/notifications.service';
import { IHALCollection } from 'core-app/core/apiv3/types/hal-collection.type';
import { ProjectsQuery } from 'core-app/core/state/projects/projects.query';
import { Apiv3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { collectionKey } from 'core-app/core/state/collection-store';
import { ProjectsStore } from './projects.store';
import { Project } from './project.model';
@Injectable()
export class ProjectsResourceService {
protected store = new ProjectsStore();
readonly query = new ProjectsQuery(this.store);
private get projectsPath():string {
return this
.apiV3Service
.projects
.path;
}
constructor(
private http:HttpClient,
private apiV3Service:APIV3Service,
private notifications:NotificationsService,
) {
}
fetchProjects(params:Apiv3ListParameters):Observable<IHALCollection<Project>> {
const collectionURL = collectionKey(params);
return this
.http
.get<IHALCollection<Project>>(this.projectsPath + collectionURL)
.pipe(
tap((events) => {
applyTransaction(() => {
this.store.add(events._embedded.elements);
this.store.update(({ collections }) => (
{
collections: {
...collections,
[collectionURL]: {
ids: events._embedded.elements.map((el) => el.id),
},
},
}
));
});
}),
catchError((error) => {
this.notifications.addError(error);
throw error;
}),
);
}
update(id:ID, project:Partial<Project>):void {
this.store.update(id, project);
}
modifyCollection(params:Apiv3ListParameters, callback:(collection:ID[]) => ID[]):void {
const key = collectionKey(params);
this.store.update(({ collections }) => (
{
collections: {
...collections,
[key]: {
...collections[key],
ids: [...callback(collections[key]?.ids || [])],
},
},
}
));
}
}

@ -0,0 +1,13 @@
import { EntityStore, StoreConfig } from '@datorama/akita';
import { CollectionState, createInitialCollectionState } from 'core-app/core/state/collection-store';
import { Project } from './project.model';
export interface ProjectsState extends CollectionState<Project> {
}
@StoreConfig({ name: 'projects' })
export class ProjectsStore extends EntityStore<ProjectsState> {
constructor() {
super(createInitialCollectionState());
}
}

@ -99,5 +99,9 @@ export class InAppNotificationCenterComponent extends UntilDestroyedMixin implem
ngOnInit():void { ngOnInit():void {
this.storeService.setFacet('unread'); this.storeService.setFacet('unread');
this.storeService.setFilters({
filter: this.uiRouterGlobals.params.filter,
name: this.uiRouterGlobals.params.name,
});
} }
} }

@ -1,9 +1,7 @@
import { Query } from '@datorama/akita'; import { Query } from '@datorama/akita';
import { import { combineLatestWith } from 'rxjs/operators';
map,
switchMap,
} from 'rxjs/operators';
import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service'; import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service';
import { ProjectsResourceService } from 'core-app/core/state/projects/projects.service';
import { InAppNotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; import { InAppNotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
import { selectCollectionAsEntities$ } from 'core-app/core/state/collection-store'; import { selectCollectionAsEntities$ } from 'core-app/core/state/collection-store';
import { import {
@ -18,6 +16,7 @@ export class IanMenuQuery extends Query<IanMenuState> {
constructor( constructor(
protected store:IanMenuStore, protected store:IanMenuStore,
protected resourceService:InAppNotificationsResourceService, protected resourceService:InAppNotificationsResourceService,
protected projectsResourceService:ProjectsResourceService,
) { ) {
super(store); super(store);
} }

@ -2,26 +2,20 @@ import {
Injectable, Injectable,
Injector, Injector,
} from '@angular/core'; } from '@angular/core';
import { import { switchMap } from 'rxjs/operators';
map,
take,
} from 'rxjs/operators';
import { from } from 'rxjs';
import { ID } from '@datorama/akita';
import { import {
markNotificationsAsRead, markNotificationsAsRead,
notificationsMarkedRead, notificationsMarkedRead,
} from 'core-app/core/state/in-app-notifications/in-app-notifications.actions'; } from 'core-app/core/state/in-app-notifications/in-app-notifications.actions';
import { InAppNotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
import { import {
EffectCallback, EffectCallback,
EffectHandler, EffectHandler,
} from 'core-app/core/state/effects/effect-handler.decorator'; } from 'core-app/core/state/effects/effect-handler.decorator';
import { ActionsService } from 'core-app/core/state/actions/actions.service'; import { ActionsService } from 'core-app/core/state/actions/actions.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service'; import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service'; import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service';
import { selectCollectionAsHrefs$ } from 'core-app/core/state/collection-store'; import { ProjectsResourceService } from 'core-app/core/state/projects/projects.service';
import { IanMenuQuery } from './ian-menu.query'; import { IanMenuQuery } from './ian-menu.query';
import { import {
IanMenuStore, IanMenuStore,
@ -36,11 +30,12 @@ export class IanMenuService {
readonly store = new IanMenuStore(); readonly store = new IanMenuStore();
readonly query = new IanMenuQuery(this.store, this.resourceService); readonly query = new IanMenuQuery(this.store, this.ianResourceService, this.projectsResourceService);
constructor( constructor(
readonly injector:Injector, readonly injector:Injector,
readonly resourceService:InAppNotificationsResourceService, readonly ianResourceService:InAppNotificationsResourceService,
readonly projectsResourceService:ProjectsResourceService,
readonly actions$:ActionsService, readonly actions$:ActionsService,
readonly apiV3Service:APIV3Service, readonly apiV3Service:APIV3Service,
) { ) {
@ -55,9 +50,15 @@ export class IanMenuService {
} }
public reload() { public reload() {
this.resourceService.fetchNotifications(IAN_MENU_PROJECT_FILTERS) this.ianResourceService.fetchNotifications(IAN_MENU_PROJECT_FILTERS)
.subscribe((data) => this.store.update({ notificationsByProject: data.groups })); .subscribe((data) => {
this.resourceService.fetchNotifications(IAN_MENU_REASON_FILTERS) this.store.update({ notificationsByProject: data.groups });
this.projectsResourceService.fetchProjects({
pageSize: 100,
filters: [['id', '=', data.groups!.map(group => idFromLink(group._links.valueLink[0].href))]],
}).subscribe();
});
this.ianResourceService.fetchNotifications(IAN_MENU_REASON_FILTERS)
.subscribe((data) => this.store.update({ notificationsByReason: data.groups })); .subscribe((data) => this.store.update({ notificationsByReason: data.groups }));
} }
} }

@ -1,10 +1,12 @@
import { Store, StoreConfig } from '@datorama/akita'; import { Store, StoreConfig } from '@datorama/akita';
import { Apiv3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface'; import { Apiv3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { NOTIFICATIONS_MAX_SIZE } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; import { NOTIFICATIONS_MAX_SIZE } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
import { Project } from 'core-app/core/state/projects/project.model';
export interface IanMenuGroupingData { export interface IanMenuGroupingData {
value:string; value:string;
count:number; count:number;
project?:Project;
_links:{ _links:{
valueLink:{ valueLink:{
href:string; href:string;

@ -1,10 +1,10 @@
import { Query } from '@datorama/akita'; import { Query } from '@datorama/akita';
import { StateService } from '@uirouter/core';
import { import {
IAN_FACET_FILTERS, IAN_FACET_FILTERS,
IanCenterState, IanCenterState,
IanCenterStore, IanCenterStore,
} from 'core-app/features/in-app-notifications/center/state/ian-center.store'; } from 'core-app/features/in-app-notifications/center/state/ian-center.store';
import { ApiV3ListFilter } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service'; import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service';
import { import {
map, map,
@ -56,14 +56,22 @@ export class IanCenterQuery extends Query<IanCenterState> {
get params():Apiv3ListParameters { get params():Apiv3ListParameters {
const state = this.store.getValue(); const state = this.store.getValue();
const filters = this.state.params; const hasFilters = state.filters.name && state.filters.filter;
return { ...state.params, filters: IAN_FACET_FILTERS[state.activeFacet] }; return {
...state.params,
filters: [
...IAN_FACET_FILTERS[state.activeFacet],
...(hasFilters
? ([[state.filters.filter, '=', [state.filters.name]]] as ApiV3ListFilter[])
: []
),
],
};
} }
constructor( constructor(
protected store:IanCenterStore, protected store:IanCenterStore,
protected resourceService:InAppNotificationsResourceService, protected resourceService:InAppNotificationsResourceService,
protected state:StateService,
) { ) {
super(store); super(store);
} }

@ -2,26 +2,22 @@ import {
Injectable, Injectable,
Injector, Injector,
} from '@angular/core'; } from '@angular/core';
import { StateService } from '@uirouter/core';
import {
IanCenterStore,
InAppNotificationFacet,
} from './ian-center.store';
import { import {
map, map,
switchMap, switchMap,
take, take,
} from 'rxjs/operators'; } from 'rxjs/operators';
import { from } from 'rxjs';
import {
ID,
setLoading,
} from '@datorama/akita';
import { import {
markNotificationsAsRead, markNotificationsAsRead,
notificationsMarkedRead, notificationsMarkedRead,
} from 'core-app/core/state/in-app-notifications/in-app-notifications.actions'; } from 'core-app/core/state/in-app-notifications/in-app-notifications.actions';
import { InAppNotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; import { InAppNotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
import { IanCenterQuery } from 'core-app/features/in-app-notifications/center/state/ian-center.query'; import { IanCenterQuery } from 'core-app/features/in-app-notifications/center/state/ian-center.query';
import {
ID,
setLoading,
} from '@datorama/akita';
import { import {
EffectCallback, EffectCallback,
EffectHandler, EffectHandler,
@ -29,9 +25,13 @@ import {
import { ActionsService } from 'core-app/core/state/actions/actions.service'; import { ActionsService } from 'core-app/core/state/actions/actions.service';
import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { HalResource } from 'core-app/features/hal/resources/hal-resource';
import { APIV3Service } from 'core-app/core/apiv3/api-v3.service'; import { APIV3Service } from 'core-app/core/apiv3/api-v3.service';
import { from } from 'rxjs';
import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service'; import { InAppNotificationsResourceService } from 'core-app/core/state/in-app-notifications/in-app-notifications.service';
import { selectCollectionAsHrefs$ } from 'core-app/core/state/collection-store'; import { selectCollectionAsHrefs$ } from 'core-app/core/state/collection-store';
import { INotificationPageQueryParameters } from 'core-app/features/in-app-notifications/in-app-notifications.routes';
import {
IanCenterStore,
InAppNotificationFacet,
} from './ian-center.store';
@Injectable() @Injectable()
@EffectHandler @EffectHandler
@ -40,20 +40,24 @@ export class IanCenterService {
readonly store = new IanCenterStore(); readonly store = new IanCenterStore();
readonly query = new IanCenterQuery(this.store, this.resourceService, this.state); readonly query = new IanCenterQuery(this.store, this.resourceService);
constructor( constructor(
readonly injector:Injector, readonly injector:Injector,
readonly resourceService:InAppNotificationsResourceService, readonly resourceService:InAppNotificationsResourceService,
readonly actions$:ActionsService, readonly actions$:ActionsService,
readonly apiV3Service:APIV3Service, readonly apiV3Service:APIV3Service,
readonly state:StateService,
) { ) {
} }
setFilters(filters:INotificationPageQueryParameters) {
this.store.update({ filters });
this.debouncedReload();
}
setFacet(facet:InAppNotificationFacet):void { setFacet(facet:InAppNotificationFacet):void {
this.store.update({ activeFacet: facet }); this.store.update({ activeFacet: facet });
this.reload(); this.debouncedReload();
} }
markAsRead(notifications:ID[]):void { markAsRead(notifications:ID[]):void {
@ -82,10 +86,12 @@ export class IanCenterService {
.resourceService .resourceService
.removeFromCollection(this.query.params, action.notifications); .removeFromCollection(this.query.params, action.notifications);
} else { } else {
this.reload(); this.debouncedReload();
} }
} }
private debouncedReload = _.debounce(this.reload.bind(this));
private reload() { private reload() {
this.resourceService this.resourceService
.fetchNotifications(this.query.params) .fetchNotifications(this.query.params)
@ -94,7 +100,7 @@ export class IanCenterService {
switchMap((results) => from(this.sideLoadInvolvedWorkPackages(results._embedded.elements))), switchMap((results) => from(this.sideLoadInvolvedWorkPackages(results._embedded.elements))),
) )
.subscribe(); .subscribe();
} };
private sideLoadInvolvedWorkPackages(elements:InAppNotification[]):Promise<unknown> { private sideLoadInvolvedWorkPackages(elements:InAppNotification[]):Promise<unknown> {
const { cache } = this.apiV3Service.work_packages; const { cache } = this.apiV3Service.work_packages;

@ -1,6 +1,7 @@
import { Store, StoreConfig } from '@datorama/akita'; import { Store, StoreConfig } from '@datorama/akita';
import { ApiV3ListFilter } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface'; import { ApiV3ListFilter } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface';
import { NOTIFICATIONS_MAX_SIZE } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; import { NOTIFICATIONS_MAX_SIZE } from 'core-app/core/state/in-app-notifications/in-app-notification.model';
import { INotificationPageQueryParameters } from 'core-app/features/in-app-notifications/in-app-notifications.routes';
export interface IanCenterState { export interface IanCenterState {
params:{ params:{
@ -8,6 +9,7 @@ export interface IanCenterState {
pageSize:number; pageSize:number;
}; };
activeFacet:InAppNotificationFacet; activeFacet:InAppNotificationFacet;
filters: INotificationPageQueryParameters;
/** Number of elements not showing after max values loaded */ /** Number of elements not showing after max values loaded */
notLoaded:number; notLoaded:number;
@ -26,6 +28,7 @@ export function createInitialState():IanCenterState {
pageSize: NOTIFICATIONS_MAX_SIZE, pageSize: NOTIFICATIONS_MAX_SIZE,
page: 1, page: 1,
}, },
filters: {},
activeFacet: 'unread', activeFacet: 'unread',
notLoaded: 0, notLoaded: 0,
}; };

Loading…
Cancel
Save