diff --git a/client/src/app/gateways/repositories/base-repository.ts b/client/src/app/gateways/repositories/base-repository.ts index b40d7cf97e..8caaaa9bc4 100644 --- a/client/src/app/gateways/repositories/base-repository.ts +++ b/client/src/app/gateways/repositories/base-repository.ts @@ -345,9 +345,6 @@ export abstract class BaseRepository { expect(findIndexInSortedArray([1, 1, 1, 1, 1, 1, 1], 1, (a, b) => a - b)).toBe(0); }); }); + + describe(`viewModelListEqual`, () => { + const els: any[] = [ + { viewModelUpdateTimestamp: 1234, id: 1 }, + { viewModelUpdateTimestamp: 1235, id: 1 }, + { viewModelUpdateTimestamp: 1234, id: 2 }, + { viewModelUpdateTimestamp: 1235, id: 2 }, + { viewModelUpdateTimestamp: 1234, id: 3 } + ]; + + it(`detects change via id switch`, () => { + const l1 = [els[0], els[2]]; + const l2 = [els[0], els[4]]; + + expect(viewModelListEqual(l1, l2)).toBeFalse(); + }); + + it(`detects change via change date switch`, () => { + const l1 = [els[0], els[2]]; + const l2 = [els[0], els[3]]; + + expect(viewModelListEqual(l1, l2)).toBeFalse(); + }); + + it(`detects change via size change`, () => { + const l1 = [els[0], els[2]]; + const l2 = [els[0]]; + + expect(viewModelListEqual(l1, l2)).toBeFalse(); + }); + + it(`detects equality`, () => { + const l1 = [els[0], els[2], els[4]]; + const l2 = [els[0], els[2], els[4]]; + + expect(viewModelListEqual(l1, l2)).toBeTrue(); + }); + }); + + describe(`viewModelEqual`, () => { + const els: any[] = [ + { viewModelUpdateTimestamp: 1234, id: 1 }, + { viewModelUpdateTimestamp: 1235, id: 1 }, + { viewModelUpdateTimestamp: 1234, id: 2 }, + { viewModelUpdateTimestamp: 1234, id: 1 } + ]; + + it(`detects change via id switch`, () => { + expect(viewModelEqual(els[0], els[2])).toBeFalse(); + }); + + it(`detects change via change date switch`, () => { + expect(viewModelEqual(els[0], els[1])).toBeFalse(); + }); + + it(`detects change via empty`, () => { + expect(viewModelEqual(els[0], null)).toBeFalse(); + }); + + it(`detects equality`, () => { + expect(viewModelEqual(els[0], els[3])).toBeTrue(); + }); + }); }); diff --git a/client/src/app/infrastructure/utils/functions.ts b/client/src/app/infrastructure/utils/functions.ts index e267166e1b..086fbf6ca6 100644 --- a/client/src/app/infrastructure/utils/functions.ts +++ b/client/src/app/infrastructure/utils/functions.ts @@ -1,4 +1,6 @@ import { Identifiable } from 'src/app/domain/interfaces'; +import { BaseModel } from 'src/app/domain/models/base/base-model'; +import { BaseViewModel } from 'src/app/site/base/base-view-model'; import { Decimal, Id } from '../../domain/definitions/key-types'; @@ -521,3 +523,25 @@ export function findIndexInSortedArray(array: T[], toFind: T, compareFn: (a: } return -1; } + +export function viewModelListEqual(l1: BaseViewModel[], l2: BaseViewModel[]): boolean { + if (l1?.length !== l2?.length) { + return false; + } + + for (let i = 0; i < l1.length; i++) { + if (!viewModelEqual(l1[i], l2[i])) { + return false; + } + } + + return true; +} + +export function viewModelEqual(vm1: BaseViewModel, vm2: BaseViewModel): boolean { + if (!vm1 || !vm2) { + return false; + } + + return vm1.viewModelUpdateTimestamp === vm2.viewModelUpdateTimestamp && vm1.id === vm2.id; +} diff --git a/client/src/app/site/modules/site-wrapper/services/voting-banner.service.ts b/client/src/app/site/modules/site-wrapper/services/voting-banner.service.ts index 8f9194b696..462baf9aa7 100644 --- a/client/src/app/site/modules/site-wrapper/services/voting-banner.service.ts +++ b/client/src/app/site/modules/site-wrapper/services/voting-banner.service.ts @@ -4,6 +4,7 @@ import { TranslateService } from '@ngx-translate/core'; import { combineLatest, distinctUntilChanged, Subscription } from 'rxjs'; import { Id } from 'src/app/domain/definitions/key-types'; import { Permission } from 'src/app/domain/definitions/permission'; +import { viewModelListEqual } from 'src/app/infrastructure/utils'; import { VoteControllerService } from 'src/app/site/pages/meetings/modules/poll/services/vote-controller.service'; import { VotingService } from 'src/app/site/pages/meetings/modules/poll/services/voting.service'; import { ViewPoll } from 'src/app/site/pages/meetings/pages/polls'; @@ -43,14 +44,7 @@ export class VotingBannerService { ) { combineLatest([ this.activeMeeting.meetingIdObservable.pipe(distinctUntilChanged()), - this.activePolls.activePollsObservable.pipe( - distinctUntilChanged((previous, current) => { - const prevStarted = previous.map(p => p.id); - const currStarted = current.map(p => p.id); - - return prevStarted.length === currStarted.length && currStarted.equals(prevStarted); - }) - ), + this.activePolls.activePollsObservable.pipe(distinctUntilChanged(viewModelListEqual)), this.meetingSettingsService.get(`users_enable_vote_delegations`).pipe(distinctUntilChanged()), this.meetingSettingsService.get(`users_forbid_delegator_to_vote`).pipe(distinctUntilChanged()), this.operator.userObservable.pipe( diff --git a/client/src/app/site/pages/meetings/modules/poll/services/poll-controller.service/poll-controller.service.ts b/client/src/app/site/pages/meetings/modules/poll/services/poll-controller.service/poll-controller.service.ts index 9bc313f797..a551d00586 100644 --- a/client/src/app/site/pages/meetings/modules/poll/services/poll-controller.service/poll-controller.service.ts +++ b/client/src/app/site/pages/meetings/modules/poll/services/poll-controller.service/poll-controller.service.ts @@ -6,6 +6,7 @@ import { Poll } from 'src/app/domain/models/poll/poll'; import { PollState } from 'src/app/domain/models/poll/poll-constants'; import { PollRepositoryService } from 'src/app/gateways/repositories/polls/poll-repository.service'; import { VoteRepositoryService } from 'src/app/gateways/repositories/polls/vote-repository.service'; +import { viewModelListEqual } from 'src/app/infrastructure/utils'; import { BaseMeetingControllerService } from 'src/app/site/pages/meetings/base/base-meeting-controller.service'; import { MeetingControllerServiceCollectorService } from 'src/app/site/pages/meetings/services/meeting-controller-service-collector.service'; @@ -22,12 +23,7 @@ export class PollControllerService extends BaseMeetingControllerService { - const prevStarted = previous.map(p => p.id); - const currStarted = current.map(p => p.id); - - return prevStarted.length === currStarted.length && currStarted.equals(prevStarted); - }), + distinctUntilChanged(viewModelListEqual), map(value => value.map(p => p.id)) ) .subscribe(startedPolls => { diff --git a/client/src/app/site/pages/meetings/pages/motions/modules/change-recommendations/services/motion-change-recommendation-controller.service/motion-change-recommendation-controller.service.ts b/client/src/app/site/pages/meetings/pages/motions/modules/change-recommendations/services/motion-change-recommendation-controller.service/motion-change-recommendation-controller.service.ts index ac5abdb4d7..d1b0f17181 100644 --- a/client/src/app/site/pages/meetings/pages/motions/modules/change-recommendations/services/motion-change-recommendation-controller.service/motion-change-recommendation-controller.service.ts +++ b/client/src/app/site/pages/meetings/pages/motions/modules/change-recommendations/services/motion-change-recommendation-controller.service/motion-change-recommendation-controller.service.ts @@ -5,6 +5,7 @@ import { Identifiable } from 'src/app/domain/interfaces'; import { MotionChangeRecommendation } from 'src/app/domain/models/motions/motion-change-recommendation'; import { ChangeRecoMode, ModificationType } from 'src/app/domain/models/motions/motions.constants'; import { MotionChangeRecommendationRepositoryService } from 'src/app/gateways/repositories/motions'; +import { viewModelListEqual } from 'src/app/infrastructure/utils'; import { BaseMeetingControllerService } from 'src/app/site/pages/meetings/base/base-meeting-controller.service'; import { MeetingControllerServiceCollectorService } from 'src/app/site/pages/meetings/services/meeting-controller-service-collector.service'; @@ -48,12 +49,7 @@ export class MotionChangeRecommendationControllerService extends BaseMeetingCont public getChangeRecosOfMotionObservable(motionId: Id): Observable { return this.getViewModelListObservable().pipe( map((recos: ViewMotionChangeRecommendation[]) => recos.filter(reco => reco.motion_id === motionId)), - distinctUntilChanged( - (prev, curr) => - prev?.length === curr?.length && - Math.max(...prev.map(e => e.viewModelUpdateTimestamp)) === - Math.max(...curr.map(e => e.viewModelUpdateTimestamp)) - ) + distinctUntilChanged(viewModelListEqual) ); } diff --git a/client/src/app/site/pages/meetings/pages/motions/services/common/amendment-controller.service/amendment-controller.service.ts b/client/src/app/site/pages/meetings/pages/motions/services/common/amendment-controller.service/amendment-controller.service.ts index 90e7b6c802..ef9828d11a 100644 --- a/client/src/app/site/pages/meetings/pages/motions/services/common/amendment-controller.service/amendment-controller.service.ts +++ b/client/src/app/site/pages/meetings/pages/motions/services/common/amendment-controller.service/amendment-controller.service.ts @@ -5,6 +5,7 @@ import { Identifiable } from 'src/app/domain/interfaces'; import { Motion } from 'src/app/domain/models/motions/motion'; import { CreateResponse } from 'src/app/gateways/repositories/base-repository'; import { MotionRepositoryService } from 'src/app/gateways/repositories/motions'; +import { viewModelListEqual } from 'src/app/infrastructure/utils'; import { ViewMotion } from '../../../view-models'; import { MotionControllerService } from '../motion-controller.service'; @@ -43,13 +44,7 @@ export class AmendmentControllerService { public getViewModelListObservableFor(motion: Identifiable): Observable { return this.getViewModelListObservable().pipe( map(_motions => _motions.filter(_motion => _motion.lead_motion_id === motion.id)), - distinctUntilChanged( - (prev, curr) => - prev?.length === curr?.length && - Math.max(...prev.map(e => e.viewModelUpdateTimestamp)) === - Math.max(...curr.map(e => e.viewModelUpdateTimestamp)) && - prev?.map(e => e.state_id).equals(curr?.map(e => e.state_id)) - ) + distinctUntilChanged(viewModelListEqual) ); } diff --git a/client/src/app/site/pages/meetings/services/active-polls.service.ts b/client/src/app/site/pages/meetings/services/active-polls.service.ts index 6dd04433ad..de808efc9b 100644 --- a/client/src/app/site/pages/meetings/services/active-polls.service.ts +++ b/client/src/app/site/pages/meetings/services/active-polls.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, distinctUntilChanged, map, Observable, Subscription } from 'rxjs'; import { Id } from 'src/app/domain/definitions/key-types'; +import { viewModelListEqual } from 'src/app/infrastructure/utils'; import { ModelRequestService } from 'src/app/site/services/model-request.service'; import { PollControllerService } from '../modules/poll/services/poll-controller.service'; @@ -44,12 +45,7 @@ export class ActivePollsService { .getViewModelListObservable() .pipe( map(polls => polls.filter(p => p.isStarted)), - distinctUntilChanged((previous, current) => { - const prevStarted = previous.map(p => p.id); - const currStarted = current.map(p => p.id); - - return prevStarted.length === currStarted.length && currStarted.equals(prevStarted); - }) + distinctUntilChanged(viewModelListEqual) ) .subscribe(polls => { this.pollIds = polls.map(poll => poll.id);