storing last run in database

This commit is contained in:
orangecoding
2026-06-09 16:52:37 +02:00
parent 6bef907416
commit bc9c56a224
9 changed files with 287 additions and 85 deletions

View File

@@ -20,6 +20,28 @@ function cap(val) {
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
}
/**
* Compute the most recent job trigger timestamp across the given jobs.
*
* Returns `null` when none of the jobs has ever been triggered. The value is
* persisted per-job via `jobs.last_run_at`, so the dashboard reflects the
* scope visible to the current user (own + shared, or all for admins) rather
* than a process-wide in-memory value.
*
* @param {Array<{lastRunAt?: number|null}>} jobs
* @returns {number|null}
*/
function computeLastRun(jobs) {
let lastRun = null;
for (const job of jobs) {
const ts = job.lastRunAt;
if (typeof ts === 'number' && (lastRun == null || ts > lastRun)) {
lastRun = ts;
}
}
return lastRun;
}
/**
* @param {import('fastify').FastifyInstance} fastify
*/
@@ -46,11 +68,13 @@ export default async function dashboardPlugin(fastify) {
}
: { labels: [], values: [] };
const lastRun = computeLastRun(jobs);
return {
general: {
interval: settings.interval,
lastRun: settings.lastRun || null,
nextRun: settings.lastRun == null ? 0 : settings.lastRun + settings.interval * 60000,
lastRun,
nextRun: lastRun == null ? 0 : lastRun + settings.interval * 60000,
},
kpis: {
totalJobs,

View File

@@ -104,7 +104,6 @@ export function initJobExecutionService({ providers, settings, intervalMs }) {
logger.debug('Working hours set. Skipping as outside of working hours.');
return;
}
settings.lastRun = now;
const jobs = jobStorage.getJobs().filter((job) => {
if (!context) return true; // startup/cron → all
if (context.isAdmin) return true; // admin → all
@@ -150,6 +149,13 @@ export function initJobExecutionService({ providers, settings, intervalMs }) {
}
const acquired = markRunning(job.id);
if (!acquired) return;
// Persist the trigger time so the dashboard "last search" KPI can be
// derived per accessible user without an in-memory cache.
try {
jobStorage.updateJobLastRunAt(job.id, Date.now());
} catch (err) {
logger.warn('Failed to persist last_run_at for job', job.id, err);
}
// notify listeners (SSE) that the job started
try {
bus.emit('jobs:status', { jobId: job.id, running: true });

View File

@@ -97,6 +97,7 @@ export const getJob = (jobId) => {
j.notification_adapter AS notificationAdapter,
j.spatial_filter AS spatialFilter,
j.spec_filter AS specFilter,
j.last_run_at AS lastRunAt,
(SELECT COUNT(1) FROM listings l WHERE l.job_id = j.id AND l.is_active = 1 AND l.manually_deleted = 0) AS numberOfFoundListings
FROM jobs j
WHERE j.id = @id
@@ -116,6 +117,24 @@ export const getJob = (jobId) => {
};
};
/**
* Record the timestamp at which a job was last triggered.
*
* Called from the job execution service when a job starts running. The value
* is persisted so that the dashboard "last search" KPI survives restarts and
* can be computed per accessible user.
*
* @param {string} jobId - Job primary key.
* @param {number} timestamp - Epoch milliseconds.
* @returns {void}
*/
export const updateJobLastRunAt = (jobId, timestamp) => {
SqliteConnection.execute(`UPDATE jobs SET last_run_at = @timestamp WHERE id = @id`, {
id: jobId,
timestamp,
});
};
/**
* Update job enabled status.
* @param {{jobId: string, status: boolean}} params - Parameters.
@@ -164,6 +183,7 @@ export const getJobs = () => {
j.notification_adapter AS notificationAdapter,
j.spatial_filter AS spatialFilter,
j.spec_filter AS specFilter,
j.last_run_at AS lastRunAt,
(SELECT COUNT(1) FROM listings l WHERE l.job_id = j.id AND l.is_active = 1 AND l.manually_deleted = 0) AS numberOfFoundListings
FROM jobs j
WHERE j.enabled = 1
@@ -269,6 +289,7 @@ export const queryJobs = ({
j.notification_adapter AS notificationAdapter,
j.spatial_filter AS spatialFilter,
j.spec_filter AS specFilter,
j.last_run_at AS lastRunAt,
(SELECT COUNT(1) FROM listings l WHERE l.job_id = j.id AND l.is_active = 1 AND l.manually_deleted = 0) AS numberOfFoundListings
FROM jobs j
${whereSql}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 by Christian Kellner.
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
*/
/**
* Migration: add `last_run_at` to the `jobs` table.
*
* Stores the epoch-ms timestamp at which a job was last triggered. Used by the
* dashboard "last search" KPI so the value survives restarts and reflects the
* actual jobs the requesting user can see (own, shared, or all for admins),
* replacing the previous in-memory `settings.lastRun` value.
*
* NULL means the job has not yet been triggered since this column was added.
*/
export function up(db) {
db.exec(`
ALTER TABLE jobs ADD COLUMN last_run_at INTEGER
`);
}

View File

@@ -18,6 +18,7 @@
* @property {SpatialFilter | null} [spatialFilter] Optional spatial filter configuration as GeoJSON FeatureCollection.
* @property {SpecFilter | null} [specFilter] Optional listing specifications.
* @property {number} [numberOfFoundListings] Count of active listings for this job.
* @property {number | null} [lastRunAt] Epoch ms at which the job was last triggered, or null if never triggered.
*/
export {};