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 {
NgModule,
} 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({
providers: [
InAppNotificationsResourceService,
ProjectsResourceService,
],
})
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 {
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 {
map,
switchMap,
} from 'rxjs/operators';
import { combineLatestWith } from 'rxjs/operators';
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 { selectCollectionAsEntities$ } from 'core-app/core/state/collection-store';
import {
@ -18,6 +16,7 @@ export class IanMenuQuery extends Query<IanMenuState> {
constructor(
protected store:IanMenuStore,
protected resourceService:InAppNotificationsResourceService,
protected projectsResourceService:ProjectsResourceService,
) {
super(store);
}

@ -2,26 +2,20 @@ import {
Injectable,
Injector,
} from '@angular/core';
import {
map,
take,
} from 'rxjs/operators';
import { from } from 'rxjs';
import { ID } from '@datorama/akita';
import { switchMap } from 'rxjs/operators';
import {
markNotificationsAsRead,
notificationsMarkedRead,
} 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 {
EffectCallback,
EffectHandler,
} from 'core-app/core/state/effects/effect-handler.decorator';
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 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 { 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 {
IanMenuStore,
@ -36,11 +30,12 @@ export class IanMenuService {
readonly store = new IanMenuStore();
readonly query = new IanMenuQuery(this.store, this.resourceService);
readonly query = new IanMenuQuery(this.store, this.ianResourceService, this.projectsResourceService);
constructor(
readonly injector:Injector,
readonly resourceService:InAppNotificationsResourceService,
readonly ianResourceService:InAppNotificationsResourceService,
readonly projectsResourceService:ProjectsResourceService,
readonly actions$:ActionsService,
readonly apiV3Service:APIV3Service,
) {
@ -55,9 +50,15 @@ export class IanMenuService {
}
public reload() {
this.resourceService.fetchNotifications(IAN_MENU_PROJECT_FILTERS)
.subscribe((data) => this.store.update({ notificationsByProject: data.groups }));
this.resourceService.fetchNotifications(IAN_MENU_REASON_FILTERS)
this.ianResourceService.fetchNotifications(IAN_MENU_PROJECT_FILTERS)
.subscribe((data) => {
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 }));
}
}

@ -1,10 +1,12 @@
import { Store, StoreConfig } from '@datorama/akita';
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 { Project } from 'core-app/core/state/projects/project.model';
export interface IanMenuGroupingData {
value:string;
count:number;
project?:Project;
_links:{
valueLink:{
href:string;

@ -1,10 +1,10 @@
import { Query } from '@datorama/akita';
import { StateService } from '@uirouter/core';
import {
IAN_FACET_FILTERS,
IanCenterState,
IanCenterStore,
} 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 {
map,
@ -56,14 +56,22 @@ export class IanCenterQuery extends Query<IanCenterState> {
get params():Apiv3ListParameters {
const state = this.store.getValue();
const filters = this.state.params;
return { ...state.params, filters: IAN_FACET_FILTERS[state.activeFacet] };
const hasFilters = state.filters.name && state.filters.filter;
return {
...state.params,
filters: [
...IAN_FACET_FILTERS[state.activeFacet],
...(hasFilters
? ([[state.filters.filter, '=', [state.filters.name]]] as ApiV3ListFilter[])
: []
),
],
};
}
constructor(
protected store:IanCenterStore,
protected resourceService:InAppNotificationsResourceService,
protected state:StateService,
) {
super(store);
}

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

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

Loading…
Cancel
Save