Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Commit

Permalink
chore(report-changes): further improvements and cleanup (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
sleidig authored Feb 19, 2024
1 parent a07b3e3 commit 38a3b0a
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 92 deletions.
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ DATABASE_USER=admin
DATABASE_PASSWORD=admin
QUERY_URL=http://127.0.0.1:4984
SCHEMA_CONFIG_ID=_design/sqlite:config
REPORT_DATABASE_URL=http://127.0.0.1:5984
REPORT_DATABASE_NAME=app

CHANGES_POLL_INTERVAL=60000
2 changes: 2 additions & 0 deletions src/couchdb/dtos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,6 @@ export interface CouchDbChangeResult {
seq: string;

doc?: any;

deleted?: boolean;
}
2 changes: 1 addition & 1 deletion src/domain/report-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class ReportData {
asHash(): string {
return crypto
.createHash('sha256')
.update(JSON.stringify(this))
.update(JSON.stringify(this.data))
.digest('hex');
}
}
12 changes: 8 additions & 4 deletions src/report-changes/core/report-change-detector.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { EntityDoc, ReportChangeDetector } from './report-change-detector';
import { ReportChangeDetector } from './report-change-detector';
import { Report } from '../../domain/report';
import { DocChangeDetails } from './report-changes.service';

import {
DocChangeDetails,
EntityDoc,
} from '../storage/database-changes.service';

describe('ReportChangeDetector', () => {
function testReportChangeDetection(
Expand All @@ -21,8 +25,8 @@ describe('ReportChangeDetector', () => {
changes: [],
seq: '',
},
new: newDoc,
previous: newDoc,
newDoc: newDoc,
previousDoc: newDoc,
};
expect(service.affectsReport(mockedDocChange)).toBe(expectedResult);
}
Expand Down
12 changes: 2 additions & 10 deletions src/report-changes/core/report-change-detector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Report } from '../../domain/report';
import { DocChangeDetails } from './report-changes.service';

import { DocChangeDetails } from '../storage/database-changes.service';

/**
* Simple class encapsulating the logic to determine if a specific report is affected by a change in the database.
Expand Down Expand Up @@ -43,12 +44,3 @@ export class ReportChangeDetector {
return true;
}
}

/**
* A doc in the database representing an entity managed in the frontend.
*/
export interface EntityDoc {
_id: string;

[key: string]: any;
}
124 changes: 62 additions & 62 deletions src/report-changes/core/report-changes.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { Injectable } from '@nestjs/common';
import { EntityDoc, ReportChangeDetector } from './report-change-detector';
import { ReportChangeDetector } from './report-change-detector';
import { NotificationService } from '../../notification/core/notification.service';
import { Reference } from '../../domain/reference';
import { ReportDataChangeEvent } from '../../domain/report-data-change-event';
import { ReportCalculationOutcomeSuccess } from '../../domain/report-calculation';
import { Report } from '../../domain/report';
import { CouchdbChangesService } from '../storage/couchdb-changes.service';
import { DefaultReportStorage } from '../../report/storage/report-storage.service';
import { filter, map, mergeAll, Observable, switchMap, tap, zip } from 'rxjs';
import { filter, map, Observable, switchMap, tap, zip } from 'rxjs';
import {
CreateReportCalculationFailed,
CreateReportCalculationUseCase,
} from '../../report/core/use-cases/create-report-calculation-use-case.service';
import { DatabaseChangeResult } from '../storage/database-changes.service';
import {
DatabaseChangeResult,
DocChangeDetails,
} from '../storage/database-changes.service';

@Injectable()
export class ReportChangesService {
Expand Down Expand Up @@ -76,13 +79,11 @@ export class ReportChangesService {

monitorCouchDbChanges() {
this.couchdbChangesRepository
.subscribeToAllNewChanges()
.subscribeToAllNewChangesWithDocs()
.pipe(
mergeAll(),
tap((change: DatabaseChangeResult) =>
this.checkReportConfigUpdate(change),
tap((change: DocChangeDetails) =>
this.checkReportConfigUpdate(change.change),
),
map((c: DatabaseChangeResult) => this.getChangeDetails(c)),
switchMap((change: DocChangeDetails) =>
this.changeIsAffectingReport(change),
),
Expand All @@ -96,23 +97,6 @@ export class ReportChangesService {
});
}

/**
* Load current and previous doc for advanced change detection across all reports.
* @param change
* @private
*/
private getChangeDetails(change: DatabaseChangeResult): DocChangeDetails {
// TODO: storage to get any doc from DB (for a _rev also!)
// until then, only the .change with the id can be used in ReportChangeDetector
// can also use ?include_docs=true in the changes request to get the latest doc

return {
change: change,
previous: { _id: '' }, // cache this here to avoid requests?
new: { _id: '' },
};
}

private changeIsAffectingReport(
docChange: DocChangeDetails,
): Observable<ReportDataChangeEvent[]> {
Expand All @@ -123,48 +107,64 @@ export class ReportChangesService {
continue;
}

const reportChangeEventObservable = this.createReportCalculation
.startReportCalculation(changeDetector.report)
.pipe(
switchMap((outcome) => {
if (outcome instanceof CreateReportCalculationFailed) {
// TODO: what do we do here in case of failure?
throw new Error('Report calculation failed');
}

return this.createReportCalculation.getCompletedReportCalculation(
new Reference(outcome.result.id),
);
}),
filter(
(calcUpdate) =>
(calcUpdate.outcome as ReportCalculationOutcomeSuccess)
?.result_hash !== changeDetector.lastCalculationHash,
),
tap(
(calcUpdate) =>
(changeDetector.lastCalculationHash = (
calcUpdate.outcome as ReportCalculationOutcomeSuccess
)?.result_hash),
),
map(
(result) =>
({
report: result.report,
calculation: result,
} as ReportDataChangeEvent),
),
);
const reportChangeEventObservable = this.calculateNewReportData(
changeDetector,
docChange,
);

affectedReports.push(reportChangeEventObservable);
}

return zip(affectedReports);
}
}

export interface DocChangeDetails {
change: DatabaseChangeResult;
previous: EntityDoc;
new: EntityDoc;
private calculateNewReportData(
changeDetector: ReportChangeDetector,
docChange: DocChangeDetails,
) {
return this.createReportCalculation
.startReportCalculation(changeDetector.report)
.pipe(
switchMap((outcome) => {
if (outcome instanceof CreateReportCalculationFailed) {
const err = new Error('Report calculation failed');
console.error(err);
// TODO: what do we do here in case of failure?
throw err;
}

return this.createReportCalculation.getCompletedReportCalculation(
new Reference(outcome.result.id),
);
}),
filter((calcUpdate) => {
if (
(calcUpdate.outcome as ReportCalculationOutcomeSuccess)
?.result_hash !== changeDetector.lastCalculationHash
) {
return true;
} else {
console.log(
'Report calculation did not change from doc',
changeDetector.report,
docChange,
);
return false;
}
}),
tap(
(calcUpdate) =>
(changeDetector.lastCalculationHash = (
calcUpdate.outcome as ReportCalculationOutcomeSuccess
)?.result_hash),
),
map(
(result) =>
({
report: result.report,
calculation: result,
} as ReportDataChangeEvent),
),
);
}
}
Loading

0 comments on commit 38a3b0a

Please sign in to comment.