(core) Update the current time in formulas automatically every hour

Summary: Adds a special user action `UpdateCurrentTime` which invalidates an internal engine dependency node that doesn't belong to any table but is 'used' by the `NOW()` function. Applies the action automatically every hour.

Test Plan: Added a Python test for the user action. Tested the interval periodically applying the action manually: {F43312}

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D3389
This commit is contained in:
Alex Hall
2022-04-25 22:31:23 +02:00
parent 0beb2898cb
commit dc9e53edc8
8 changed files with 105 additions and 13 deletions

View File

@@ -119,6 +119,9 @@ const MEMORY_MEASUREMENT_INTERVAL_MS = 60 * 1000;
// Cleanup expired attachments every hour (also happens when shutting down)
const REMOVE_UNUSED_ATTACHMENTS_INTERVAL_MS = 60 * 60 * 1000;
// Apply the UpdateCurrentTime user action every hour
const UPDATE_CURRENT_TIME_INTERVAL_MS = 60 * 60 * 1000;
// A hook for dependency injection.
export const Deps = {ACTIVEDOC_TIMEOUT};
@@ -180,11 +183,18 @@ export class ActiveDoc extends EventEmitter {
private _recoveryMode: boolean = false;
private _shuttingDown: boolean = false;
// Cleanup expired attachments every hour (also happens when shutting down)
private _removeUnusedAttachmentsInterval = setInterval(
() => this.removeUnusedAttachments(true),
REMOVE_UNUSED_ATTACHMENTS_INTERVAL_MS,
);
// Intervals to clear on shutdown
private _intervals = [
// Cleanup expired attachments every hour (also happens when shutting down)
setInterval(
() => this.removeUnusedAttachments(true),
REMOVE_UNUSED_ATTACHMENTS_INTERVAL_MS,
),
setInterval(
() => this._applyUserActions(makeExceptionalDocSession('system'), [["UpdateCurrentTime"]]),
UPDATE_CURRENT_TIME_INTERVAL_MS,
),
];
constructor(docManager: DocManager, docName: string, private _options?: ICreateActiveDocOptions) {
super();
@@ -406,7 +416,10 @@ export class ActiveDoc extends EventEmitter {
// Clear the MapWithTTL to remove all timers from the event loop.
this._fetchCache.clear();
clearInterval(this._removeUnusedAttachmentsInterval);
for (const interval of this._intervals) {
clearInterval(interval);
}
try {
// Remove expired attachments, i.e. attachments that were soft deleted a while ago.
// This needs to happen periodically, and doing it here means we can guarantee that it happens even if

View File

@@ -99,7 +99,7 @@ const SURPRISING_ACTIONS = new Set([
]);
// Actions we'll allow unconditionally for now.
const OK_ACTIONS = new Set(['Calculate']);
const OK_ACTIONS = new Set(['Calculate', 'UpdateCurrentTime']);
/**
* Granular access for a single bundle, in different phases.

View File

@@ -216,9 +216,9 @@ export class Sharing {
try {
const isCalculate = (userActions.length === 1 &&
userActions[0][0] === 'Calculate');
(userActions[0][0] === 'Calculate' || userActions[0][0] === 'UpdateCurrentTime'));
// `internal` is true if users shouldn't be able to undo the actions. Applies to:
// - Calculate because it's not considered as performed by a particular client.
// - Calculate/UpdateCurrentTime because it's not considered as performed by a particular client.
// - Adding attachment metadata when uploading attachments,
// because then the attachment file may get hard-deleted and redo won't work properly.
const internal = isCalculate || userActions.every(a => a[0] === "AddRecord" && a[1] === "_grist_Attachments");