mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-15 12:23:51 +00:00
Report (#1469)
* create report service * write report from runtimeState * use in UI * clear report * update types * clear all from settings menu * rearence rightclik menu * refactor styling * also report roll events * ontime/under time is same colour * refactor reporter * add target to ontime-refetch * remove menu * fectch only on message from server * memo useGetEventReport * refactor * use staleTime * dont add to menu yet * implement review * clear all from menu * fix merge * start end show * combine test for the go button text and action * add report to menu * extract csv utility * report management * unneeded async * small refacort of triggerReportEntry --------- Co-authored-by: Carlos Valente <carlosvalente@pm.me>
This commit is contained in:
committed by
GitHub
parent
21877e4bff
commit
b6507c27a6
@@ -11,6 +11,7 @@ import { router as sheetsRouter } from './sheets/sheets.router.js';
|
||||
import { router as excelRouter } from './excel/excel.router.js';
|
||||
import { router as sessionRouter } from './session/session.router.js';
|
||||
import { router as viewSettingsRouter } from './view-settings/viewSettings.router.js';
|
||||
import { router as reportRouter } from './report/report.router.js';
|
||||
|
||||
export const appRouter = express.Router();
|
||||
|
||||
@@ -25,6 +26,7 @@ appRouter.use('/excel', excelRouter);
|
||||
appRouter.use('/url-presets', urlPresetsRouter);
|
||||
appRouter.use('/session', sessionRouter);
|
||||
appRouter.use('/view-settings', viewSettingsRouter);
|
||||
appRouter.use('/report', reportRouter);
|
||||
|
||||
//we don't want to redirect to react index when using api routes
|
||||
appRouter.all('/*', (_req, res) => {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Request, Response } from 'express';
|
||||
import type { OntimeReport } from 'ontime-types';
|
||||
import * as report from './report.service.js';
|
||||
|
||||
export function getAll(_req: Request, res: Response<OntimeReport>) {
|
||||
res.json(report.generate());
|
||||
}
|
||||
|
||||
export function deleteAll(_req: Request, res: Response<OntimeReport>) {
|
||||
report.clear();
|
||||
res.status(200).send();
|
||||
}
|
||||
|
||||
export function deleteWithId(req: Request, res: Response<OntimeReport>) {
|
||||
const { eventId } = req.params;
|
||||
report.clear(eventId);
|
||||
res.status(200).send();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import express from 'express';
|
||||
import { getAll, deleteWithId, deleteAll } from './report.controller.js';
|
||||
import { paramsMustHaveEventId } from '../rundown/rundown.validation.js';
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
router.get('/', getAll);
|
||||
|
||||
router.delete('/all', deleteAll);
|
||||
router.delete('/:eventId', paramsMustHaveEventId, deleteWithId);
|
||||
@@ -0,0 +1,65 @@
|
||||
import { OntimeReport, OntimeEventReport, TimerLifeCycle } from 'ontime-types';
|
||||
import { RuntimeState } from '../../stores/runtimeState.js';
|
||||
import { sendRefetch } from '../../adapters/websocketAux.js';
|
||||
import { DeepReadonly } from 'ts-essentials';
|
||||
|
||||
const report = new Map<string, OntimeEventReport>();
|
||||
|
||||
let formattedReport: OntimeReport | null = null;
|
||||
|
||||
/**
|
||||
* generates a full report
|
||||
* @returns full report
|
||||
*/
|
||||
export function generate(): OntimeReport {
|
||||
if (formattedReport === null) {
|
||||
formattedReport = Object.fromEntries(report);
|
||||
}
|
||||
return formattedReport;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear report
|
||||
* @param id optional id of a event report to clear
|
||||
*/
|
||||
export function clear(id?: string) {
|
||||
formattedReport = null;
|
||||
if (id) {
|
||||
report.delete(id);
|
||||
} else {
|
||||
report.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* trigger report entry
|
||||
* @param cycle
|
||||
* @param state
|
||||
* @returns
|
||||
*/
|
||||
export function triggerReportEntry(
|
||||
cycle: TimerLifeCycle.onStart | TimerLifeCycle.onStop,
|
||||
state: DeepReadonly<RuntimeState>,
|
||||
) {
|
||||
if (!state.eventNow?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventId = state.eventNow.id;
|
||||
|
||||
if (cycle === TimerLifeCycle.onStart) {
|
||||
report.set(eventId, { startedAt: state.timer.startedAt, endedAt: null });
|
||||
formattedReport = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (cycle === TimerLifeCycle.onStop) {
|
||||
const startedAt = report.get(eventId)?.startedAt ?? null;
|
||||
report.set(eventId, { startedAt, endedAt: state.clock });
|
||||
formattedReport = null;
|
||||
sendRefetch({
|
||||
target: 'REPORT',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -266,6 +266,7 @@ function notifyChanges(options: NotifyChangesOptions) {
|
||||
if (options.external) {
|
||||
// advice socket subscribers of change
|
||||
const payload = {
|
||||
target: 'RUNDOWN',
|
||||
changes: Array.isArray(options.timer) ? options.timer : undefined,
|
||||
reload: options.reload,
|
||||
revision: cache.getMetadata().revision,
|
||||
|
||||
@@ -20,6 +20,8 @@ import type { RuntimeState } from '../../stores/runtimeState.js';
|
||||
import { timerConfig } from '../../config/config.js';
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
|
||||
import { triggerReportEntry } from '../../api-data/report/report.service.js';
|
||||
|
||||
import { EventTimer } from '../EventTimer.js';
|
||||
import { RestorePoint, restoreService } from '../RestoreService.js';
|
||||
import {
|
||||
@@ -288,14 +290,17 @@ class RuntimeService {
|
||||
logger.warning(LogOrigin.Playback, `Refused skipped event with ID ${event.id}`);
|
||||
return false;
|
||||
}
|
||||
const previousState = runtimeState.getState();
|
||||
|
||||
const rundown = getRundown();
|
||||
const success = runtimeState.load(event, rundown, initialData);
|
||||
|
||||
if (success) {
|
||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
|
||||
const newState = runtimeState.getState();
|
||||
process.nextTick(() => {
|
||||
triggerAutomations(TimerLifeCycle.onLoad, runtimeState.getState());
|
||||
triggerReportEntry(TimerLifeCycle.onStop, previousState);
|
||||
triggerAutomations(TimerLifeCycle.onLoad, newState);
|
||||
});
|
||||
}
|
||||
return success;
|
||||
@@ -474,6 +479,7 @@ class RuntimeService {
|
||||
|
||||
if (didStart) {
|
||||
process.nextTick(() => {
|
||||
triggerReportEntry(TimerLifeCycle.onStart, newState);
|
||||
triggerAutomations(TimerLifeCycle.onStart, newState);
|
||||
});
|
||||
}
|
||||
@@ -536,8 +542,8 @@ class RuntimeService {
|
||||
*/
|
||||
@broadcastResult
|
||||
public stop(): boolean {
|
||||
const state = runtimeState.getState();
|
||||
const canStop = validatePlayback(state.timer.playback, state.timer.phase).stop;
|
||||
const previousState = runtimeState.getState();
|
||||
const canStop = validatePlayback(previousState.timer.playback, previousState.timer.phase).stop;
|
||||
if (!canStop) {
|
||||
return false;
|
||||
}
|
||||
@@ -546,6 +552,7 @@ class RuntimeService {
|
||||
const newState = runtimeState.getState();
|
||||
logger.info(LogOrigin.Playback, `Play Mode ${newState.timer.playback.toUpperCase()}`);
|
||||
process.nextTick(() => {
|
||||
triggerReportEntry(TimerLifeCycle.onStop, previousState);
|
||||
triggerAutomations(TimerLifeCycle.onStop, newState);
|
||||
});
|
||||
|
||||
@@ -598,12 +605,14 @@ class RuntimeService {
|
||||
if (result.eventId !== previousState.eventNow?.id) {
|
||||
logger.info(LogOrigin.Playback, `Loaded event with ID ${result.eventId}`);
|
||||
process.nextTick(() => {
|
||||
triggerReportEntry(TimerLifeCycle.onStop, previousState);
|
||||
triggerAutomations(TimerLifeCycle.onLoad, newState);
|
||||
});
|
||||
}
|
||||
|
||||
if (result.didStart) {
|
||||
process.nextTick(() => {
|
||||
triggerReportEntry(TimerLifeCycle.onStart, newState);
|
||||
triggerAutomations(TimerLifeCycle.onStart, newState);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -391,7 +391,6 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
||||
// update offset
|
||||
state.runtime.offset = getRuntimeOffset(state);
|
||||
state.runtime.expectedEnd = state.runtime.plannedEnd - state.runtime.offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -410,7 +409,6 @@ export function stop(state: RuntimeState = runtimeState): boolean {
|
||||
if (state.timer.playback === Playback.Stop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clear();
|
||||
runtimeState.runtime.actualStart = null;
|
||||
runtimeState.runtime.expectedEnd = null;
|
||||
|
||||
Reference in New Issue
Block a user