Skip to content

Commit

Permalink
Store most recent quiz row timestamp + sync to minimise extra process…
Browse files Browse the repository at this point in the history
…ing & for display so I can quickly see stuff is working
  • Loading branch information
Lan2u committed Sep 17, 2024
1 parent 66bb749 commit b666989
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 49 deletions.
83 changes: 42 additions & 41 deletions src/read-models/shared-state/async-apply-external-event-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,53 @@ import {sheets_v4} from '@googleapis/sheets';
import {Equipment} from './return-types';
import {QzEvent} from '../../types/qz-event';
import {extractGoogleSheetData} from '../../training-sheets/google';
import {isNewQuizEvents} from './is-new-quiz-events';

const GOOGLE_UPDATE_INTERVAL_MS = 5 * 60 * 1000;

export type PullSheetData = (
logger: Logger,
trainingSheetId: string
trainingSheetId: string,
rowsSince: O.Option<Date>
) => TE.TaskEither<Failure, sheets_v4.Schema$Spreadsheet>;

const pullNewEquipmentQuizResults =
(logger: Logger, pullGoogleSheetData: PullSheetData) =>
(equipment: Equipment): T.Task<ReadonlyArray<QzEvent>> => {
if (O.isNone(equipment.trainingSheetId)) {
logger.warn(
'No training sheet registered for equipment %s, skipping training data ingestion',
equipment.name
);
// eslint-disable-next-line @typescript-eslint/require-await
return async () => [] as ReadonlyArray<QzEvent>;
}
const trainingSheetId = equipment.trainingSheetId.value;
logger.info(
`Scanning training sheet ${trainingSheetId}. Pulling google sheet data...`
);
return pipe(
pullGoogleSheetData(logger, trainingSheetId),
TE.map(
extractGoogleSheetData(
logger.child({trainingSheetId: trainingSheetId}),
trainingSheetId
)
),
TE.map(RA.flatten),
// eslint-disable-next-line @typescript-eslint/require-await
TE.getOrElse(err => async () => {
logger.error(
'Failed to receive data from google sheets for equipment %s training sheet %o: %s',
equipment.name,
equipment.trainingSheetId,
err.message
);
return [] as ReadonlyArray<QzEvent>;
}),
T.map(RA.filter(isNewQuizEvents(equipment)))
const pullNewEquipmentQuizResults = (
logger: Logger,
pullGoogleSheetData: PullSheetData,
equipment: Equipment
): T.Task<ReadonlyArray<QzEvent>> => {
if (O.isNone(equipment.trainingSheetId)) {
logger.warn(
'No training sheet registered for equipment %s, skipping training data ingestion',
equipment.name
);
};
// eslint-disable-next-line @typescript-eslint/require-await
return async () => [] as ReadonlyArray<QzEvent>;
}
const trainingSheetId = equipment.trainingSheetId.value;
logger.info(
`Scanning training sheet ${trainingSheetId}. Pulling google sheet data...`
);
return pipe(
pullGoogleSheetData(logger, trainingSheetId, equipment.lastQuizResult),
TE.map(
extractGoogleSheetData(
logger.child({trainingSheetId: trainingSheetId}),
trainingSheetId
)
),
TE.map(RA.flatten),
// eslint-disable-next-line @typescript-eslint/require-await
TE.getOrElse(err => async () => {
logger.error(
'Failed to receive data from google sheets for equipment %s training sheet %o: %s',
equipment.name,
equipment.trainingSheetId,
err.message
);
return [] as ReadonlyArray<QzEvent>;
})
);
};

export const asyncApplyExternalEventSources = (
logger: Logger,
Expand All @@ -65,7 +66,6 @@ export const asyncApplyExternalEventSources = (
updateState: (event: DomainEvent) => void
) => {
let lastGoogleUpdate: number | null;

return () => async () => {
logger.info('Applying external event sources...');
if (
Expand All @@ -77,8 +77,9 @@ export const asyncApplyExternalEventSources = (
RA.map(updateState)(
await pullNewEquipmentQuizResults(
logger,
pullGoogleSheetData
)(equipment)()
pullGoogleSheetData,
equipment
)()
);
}
}
Expand Down
8 changes: 0 additions & 8 deletions src/read-models/shared-state/is-new-quiz-events.ts

This file was deleted.

6 changes: 6 additions & 0 deletions src/read-models/shared-state/return-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ export type Equipment = {
orphanedPassedQuizes: ReadonlyArray<OrphanedPassedQuiz>;
failedQuizAttempts: ReadonlyArray<FailedQuizAttempt>;
trainingSheetId: O.Option<string>;

// Uses the actual spreadsheet timestamp rather than our local timestamp which could be
// different due to clock drift or eventual consistency issues on the google side.
lastQuizResult: O.Option<Date>;
// Uses local timestamp.
lastQuizSync: O.Option<Date>;
};

type TrainedOn = {
Expand Down

0 comments on commit b666989

Please sign in to comment.