mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Remove transform columns on shutdown
Summary: Call a new user action `RemoveTransformColumns` in ActiveDoc shutdown. Test Plan: Added nbrowser test Reviewers: georgegevoian, paulfitz Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D4107
This commit is contained in:
parent
3dfe4be5f3
commit
5197891427
@ -145,8 +145,22 @@ export interface TableRecordValue {
|
|||||||
|
|
||||||
export type UserAction = Array<string|number|object|boolean|null|undefined>;
|
export type UserAction = Array<string|number|object|boolean|null|undefined>;
|
||||||
|
|
||||||
// Actions that trigger formula calculations in the data engine
|
// Actions that are performed automatically by the server
|
||||||
export const CALCULATING_USER_ACTIONS = new Set(['Calculate', 'UpdateCurrentTime', 'RespondToRequests']);
|
// for things like regular maintenance or triggering formula calculations in the data engine.
|
||||||
|
// Typically applied using `makeExceptionalDocSession("system")`.
|
||||||
|
// They're also 'non-essential' in the sense that we don't need to worry about storing them
|
||||||
|
// in action/undo history if they don't change anything (which they often won't)
|
||||||
|
// and we can dismiss their result if the document is shutting down.
|
||||||
|
export const SYSTEM_ACTIONS = new Set([
|
||||||
|
// Initial dummy action performed when the document laods.
|
||||||
|
'Calculate',
|
||||||
|
// Called automatically at regular intervals, again to trigger formula calculations.
|
||||||
|
'UpdateCurrentTime',
|
||||||
|
// Part of the formula calculation process for formulas that use the `REQUEST` function.
|
||||||
|
'RespondToRequests',
|
||||||
|
// Performed at shutdown to clean up temporary helper columns.
|
||||||
|
'RemoveTransformColumns'
|
||||||
|
]);
|
||||||
|
|
||||||
export function getNumRows(action: DocAction): number {
|
export function getNumRows(action: DocAction): number {
|
||||||
return !isDataAction(action) ? 0
|
return !isDataAction(action) ? 0
|
||||||
|
@ -1994,6 +1994,14 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
// even if the doc is only ever opened briefly, without having to slow down startup.
|
// even if the doc is only ever opened briefly, without having to slow down startup.
|
||||||
await safeCallAndWait("removeUnusedAttachments", () => this.removeUnusedAttachments(true, usageOptions));
|
await safeCallAndWait("removeUnusedAttachments", () => this.removeUnusedAttachments(true, usageOptions));
|
||||||
|
|
||||||
|
if (this._dataEngine && this._fullyLoaded) {
|
||||||
|
// Note that this must happen before `this._shuttingDown = true` because of this line in Sharing.ts:
|
||||||
|
// if (this._activeDoc.isShuttingDown && isCalculate) {
|
||||||
|
await safeCallAndWait("removeTransformColumns",
|
||||||
|
() => this.applyUserActions(docSession, [["RemoveTransformColumns"]])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update data size; we'll be syncing both it and attachments size to the database soon.
|
// Update data size; we'll be syncing both it and attachments size to the database soon.
|
||||||
await safeCallAndWait("_updateDataSize", () => this._updateDataSize(usageOptions));
|
await safeCallAndWait("_updateDataSize", () => this._updateDataSize(usageOptions));
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
UserActionBundle
|
UserActionBundle
|
||||||
} from 'app/common/ActionBundle';
|
} from 'app/common/ActionBundle';
|
||||||
import {ApplyUAExtendedOptions} from 'app/common/ActiveDocAPI';
|
import {ApplyUAExtendedOptions} from 'app/common/ActiveDocAPI';
|
||||||
import {CALCULATING_USER_ACTIONS, DocAction, getNumRows, UserAction} from 'app/common/DocActions';
|
import {DocAction, getNumRows, SYSTEM_ACTIONS, UserAction} from 'app/common/DocActions';
|
||||||
import {allToken} from 'app/common/sharing';
|
import {allToken} from 'app/common/sharing';
|
||||||
import {GranularAccessForBundle} from 'app/server/lib/GranularAccess';
|
import {GranularAccessForBundle} from 'app/server/lib/GranularAccess';
|
||||||
import log from 'app/server/lib/log';
|
import log from 'app/server/lib/log';
|
||||||
@ -246,7 +246,7 @@ export class Sharing {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const isCalculate = (userActions.length === 1 && CALCULATING_USER_ACTIONS.has(userActions[0][0] as string));
|
const isCalculate = (userActions.length === 1 && SYSTEM_ACTIONS.has(userActions[0][0] as string));
|
||||||
// `internal` is true if users shouldn't be able to undo the actions. Applies to:
|
// `internal` is true if users shouldn't be able to undo the actions. Applies to:
|
||||||
// - Calculate/UpdateCurrentTime 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,
|
// - Adding attachment metadata when uploading attachments,
|
||||||
|
@ -1246,10 +1246,7 @@ class UserActions(object):
|
|||||||
# Remove also all autogenereted formula columns for conditional styles.
|
# Remove also all autogenereted formula columns for conditional styles.
|
||||||
# But not from transform columns, as those columns borrow rules from original columns
|
# But not from transform columns, as those columns borrow rules from original columns
|
||||||
more_removals.update([rule
|
more_removals.update([rule
|
||||||
for col in col_recs if not col.colId.startswith((
|
for col in col_recs if not _is_transform_col(col.colId)
|
||||||
'gristHelper_Transform',
|
|
||||||
'gristHelper_Converted',
|
|
||||||
))
|
|
||||||
for rule in col.rules])
|
for rule in col.rules])
|
||||||
|
|
||||||
# Add any extra removals after removing the requested columns in the requested order.
|
# Add any extra removals after removing the requested columns in the requested order.
|
||||||
@ -1353,10 +1350,7 @@ class UserActions(object):
|
|||||||
|
|
||||||
transform = (
|
transform = (
|
||||||
col_id is not None and
|
col_id is not None and
|
||||||
col_id.startswith((
|
_is_transform_col(col_id)
|
||||||
'gristHelper_Transform',
|
|
||||||
'gristHelper_Converted',
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ret = self.doAddColumn(table_id, col_id, col_info)
|
ret = self.doAddColumn(table_id, col_id, col_info)
|
||||||
@ -1387,11 +1381,8 @@ class UserActions(object):
|
|||||||
table_rec = self._docmodel.get_table_rec(table_id)
|
table_rec = self._docmodel.get_table_rec(table_id)
|
||||||
|
|
||||||
transform = (
|
transform = (
|
||||||
col_id is not None and
|
col_id is not None and
|
||||||
col_id.startswith((
|
_is_transform_col(col_id)
|
||||||
'gristHelper_Transform',
|
|
||||||
'gristHelper_Converted',
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add a field for this column to the view(s) for this table.
|
# Add a field for this column to the view(s) for this table.
|
||||||
@ -1404,7 +1395,6 @@ class UserActions(object):
|
|||||||
self._docmodel.insert(section.fields, col_info.get('_position'), colRef=ret['colRef'])
|
self._docmodel.insert(section.fields, col_info.get('_position'), colRef=ret['colRef'])
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _pick_col_name(cls, table_rec, col_id, old_col_id=None, avoid_extra=None):
|
def _pick_col_name(cls, table_rec, col_id, old_col_id=None, avoid_extra=None):
|
||||||
avoid_set = set(c.colId for c in table_rec.columns)
|
avoid_set = set(c.colId for c in table_rec.columns)
|
||||||
@ -1490,6 +1480,12 @@ class UserActions(object):
|
|||||||
# Update the col's displayCol ref
|
# Update the col's displayCol ref
|
||||||
self._docmodel.update([col_rec], displayCol=display_col_ref)
|
self._docmodel.update([col_rec], displayCol=display_col_ref)
|
||||||
|
|
||||||
|
@useraction
|
||||||
|
def RemoveTransformColumns(self):
|
||||||
|
self._docmodel.remove([
|
||||||
|
col for col in self._docmodel.columns.all if _is_transform_col(col.colId)
|
||||||
|
])
|
||||||
|
|
||||||
# Helper function to get a helper column with the given formula, or to add one if none
|
# Helper function to get a helper column with the given formula, or to add one if none
|
||||||
# currently exist.
|
# currently exist.
|
||||||
def _add_or_update_helper_col(self, table_rec, display_col_rec, formula):
|
def _add_or_update_helper_col(self, table_rec, display_col_rec, formula):
|
||||||
@ -2208,3 +2204,10 @@ class UserActions(object):
|
|||||||
def GenImporterView(self, source_table_id, dest_table_id, transform_rule=None, options=None):
|
def GenImporterView(self, source_table_id, dest_table_id, transform_rule=None, options=None):
|
||||||
return self._import_actions.DoGenImporterView(
|
return self._import_actions.DoGenImporterView(
|
||||||
source_table_id, dest_table_id, transform_rule, options or {})
|
source_table_id, dest_table_id, transform_rule, options or {})
|
||||||
|
|
||||||
|
|
||||||
|
def _is_transform_col(col_id):
|
||||||
|
return col_id.startswith((
|
||||||
|
'gristHelper_Transform',
|
||||||
|
'gristHelper_Converted',
|
||||||
|
))
|
||||||
|
BIN
test/fixtures/docs/RemoveTransformColumns.grist
vendored
Normal file
BIN
test/fixtures/docs/RemoveTransformColumns.grist
vendored
Normal file
Binary file not shown.
35
test/nbrowser/RemoveTransformColumns.ts
Normal file
35
test/nbrowser/RemoveTransformColumns.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {assert, driver} from 'mocha-webdriver';
|
||||||
|
|
||||||
|
import * as gu from 'test/nbrowser/gristUtils';
|
||||||
|
import {server, setupTestSuite} from "test/nbrowser/testUtils";
|
||||||
|
|
||||||
|
describe('RemoveTransformColumns', function () {
|
||||||
|
this.timeout(4000);
|
||||||
|
setupTestSuite();
|
||||||
|
|
||||||
|
it('should remove transform columns when the doc shuts down', async function () {
|
||||||
|
await server.simulateLogin("Chimpy", "chimpy@getgrist.com", 'nasa');
|
||||||
|
const doc = await gu.importFixturesDoc('chimpy', 'nasa', 'Horizon', 'RemoveTransformColumns.grist', false);
|
||||||
|
await driver.get(`${server.getHost()}/o/nasa/doc/${doc.id}`);
|
||||||
|
await gu.waitForDocToLoad();
|
||||||
|
|
||||||
|
assert.deepEqual(await gu.getVisibleGridCells({col: 'B', rowNums: [1]}), [
|
||||||
|
'manualSort, A, B, C, ' +
|
||||||
|
'gristHelper_Converted, gristHelper_Transform, ' +
|
||||||
|
'gristHelper_Converted2, gristHelper_Transform2'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const userAPI = gu.createHomeApi('chimpy', 'nasa');
|
||||||
|
await userAPI.applyUserActions(doc.id, [["Calculate"]]); // finish loading fully
|
||||||
|
await userAPI.getDocAPI(doc.id).forceReload();
|
||||||
|
await driver.get(`${server.getHost()}/o/nasa/doc/${doc.id}`);
|
||||||
|
await gu.waitForDocToLoad();
|
||||||
|
|
||||||
|
assert.deepEqual(await gu.getVisibleGridCells({col: 'B', rowNums: [1]}), [
|
||||||
|
'manualSort, A, B, C'
|
||||||
|
]);
|
||||||
|
|
||||||
|
await gu.checkForErrors();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -67,7 +67,7 @@ class CaptureTransport extends winston.Transport {
|
|||||||
private _captureFunc: (level: string, msg: string, meta: any) => void;
|
private _captureFunc: (level: string, msg: string, meta: any) => void;
|
||||||
|
|
||||||
public constructor(options: any) {
|
public constructor(options: any) {
|
||||||
super();
|
super(options);
|
||||||
this._captureFunc = options.captureFunc;
|
this._captureFunc = options.captureFunc;
|
||||||
if (options.name) {
|
if (options.name) {
|
||||||
this.name = options.name;
|
this.name = options.name;
|
||||||
@ -125,7 +125,8 @@ export function setTmpLogLevel(level: string, optCaptureFunc?: (level: string, m
|
|||||||
* strings. These may be tested using testUtils.assertMatchArray(). Callback may return a promise.
|
* strings. These may be tested using testUtils.assertMatchArray(). Callback may return a promise.
|
||||||
*/
|
*/
|
||||||
export async function captureLog(
|
export async function captureLog(
|
||||||
minLevel: string, callback: (messages: string[]) => void|Promise<void>
|
minLevel: string, callback: (messages: string[]) => void|Promise<void>,
|
||||||
|
options: {timestamp: boolean} = {timestamp: false}
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
const messages: string[] = [];
|
const messages: string[] = [];
|
||||||
const prevLogLevel = log.transports.file.level;
|
const prevLogLevel = log.transports.file.level;
|
||||||
@ -133,14 +134,15 @@ export async function captureLog(
|
|||||||
|
|
||||||
function capture(level: string, msg: string, meta: any) {
|
function capture(level: string, msg: string, meta: any) {
|
||||||
if ((log as any).levels[level] <= (log as any).levels[minLevel]) { // winston types are off?
|
if ((log as any).levels[level] <= (log as any).levels[minLevel]) { // winston types are off?
|
||||||
messages.push(level + ': ' + msg + (meta ? ' ' + serialize(meta) : ''));
|
const timePrefix = options.timestamp ? new Date().toISOString() + ' ' : '';
|
||||||
|
messages.push(`${timePrefix}${level}: ${msg}${meta ? ' ' + serialize(meta) : ''}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!process.env.VERBOSE) {
|
if (!process.env.VERBOSE) {
|
||||||
log.transports.file.level = -1 as any; // Suppress all log output.
|
log.transports.file.level = -1 as any; // Suppress all log output.
|
||||||
}
|
}
|
||||||
log.add(CaptureTransport as any, { captureFunc: capture, name }); // types are off.
|
log.add(CaptureTransport as any, { captureFunc: capture, name, level: minLevel}); // types are off.
|
||||||
try {
|
try {
|
||||||
await callback(messages);
|
await callback(messages);
|
||||||
} finally {
|
} finally {
|
||||||
|
Loading…
Reference in New Issue
Block a user