mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Show count of formula errors in the column config in the right-side panel.
Summary: - Cache the count by column, factoring out ColumnCache from ColumnACIndexes, which uses a similar pattern. - Update error counts in response to column selection and to data changes. Test Plan: Adds a test case for the new message Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2780
This commit is contained in:
@@ -9,11 +9,10 @@
|
||||
* It is currently used for auto-complete in the ReferenceEditor widget.
|
||||
*/
|
||||
import {ACIndex, ACIndexImpl} from 'app/client/lib/ACIndex';
|
||||
import {ColumnCache} from 'app/client/models/ColumnCache';
|
||||
import {UserError} from 'app/client/models/errors';
|
||||
import {TableData} from 'app/client/models/TableData';
|
||||
import {DocAction} from 'app/common/DocActions';
|
||||
import {isBulkUpdateRecord, isUpdateRecord} from 'app/common/DocActions';
|
||||
import {getSetMapValue, localeCompare, nativeCompare} from 'app/common/gutil';
|
||||
import {localeCompare, nativeCompare} from 'app/common/gutil';
|
||||
import {BaseFormatter} from 'app/common/ValueFormatter';
|
||||
|
||||
export interface ICellItem {
|
||||
@@ -24,13 +23,9 @@ export interface ICellItem {
|
||||
|
||||
|
||||
export class ColumnACIndexes {
|
||||
private _cachedColIndexes = new Map<string, ACIndex<ICellItem>>();
|
||||
private _columnCache = new ColumnCache<ACIndex<ICellItem>>(this._tableData);
|
||||
|
||||
constructor(private _tableData: TableData) {
|
||||
// Whenever a table action is applied, consider invalidating per-column caches.
|
||||
this._tableData.tableActionEmitter.addListener(this._invalidateCache, this);
|
||||
this._tableData.dataLoadedEmitter.addListener(this._clearCache, this);
|
||||
}
|
||||
constructor(private _tableData: TableData) {}
|
||||
|
||||
/**
|
||||
* Returns the column index for the given column, using a cached one if available.
|
||||
@@ -38,7 +33,7 @@ export class ColumnACIndexes {
|
||||
* getColACIndex() is called for the same column with the the same formatter.
|
||||
*/
|
||||
public getColACIndex(colId: string, formatter: BaseFormatter): ACIndex<ICellItem> {
|
||||
return getSetMapValue(this._cachedColIndexes, colId, () => this._buildColACIndex(colId, formatter));
|
||||
return this._columnCache.getValue(colId, () => this._buildColACIndex(colId, formatter));
|
||||
}
|
||||
|
||||
private _buildColACIndex(colId: string, formatter: BaseFormatter): ACIndex<ICellItem> {
|
||||
@@ -56,23 +51,6 @@ export class ColumnACIndexes {
|
||||
items.sort(itemCompare);
|
||||
return new ACIndexImpl(items);
|
||||
}
|
||||
|
||||
private _invalidateCache(action: DocAction): void {
|
||||
if (isUpdateRecord(action) || isBulkUpdateRecord(action)) {
|
||||
// If the update only affects existing records, only invalidate affected columns.
|
||||
const colValues = action[3];
|
||||
for (const colId of Object.keys(colValues)) {
|
||||
this._cachedColIndexes.delete(colId);
|
||||
}
|
||||
} else {
|
||||
// For add/delete actions and all schema changes, drop the cache entirelly to be on the safe side.
|
||||
this._clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
private _clearCache(): void {
|
||||
this._cachedColIndexes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
function itemCompare(a: ICellItem, b: ICellItem) {
|
||||
|
||||
42
app/client/models/ColumnCache.ts
Normal file
42
app/client/models/ColumnCache.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Implements a cache of values computed from the data in a Grist column.
|
||||
*/
|
||||
import {TableData} from 'app/client/models/TableData';
|
||||
import {DocAction} from 'app/common/DocActions';
|
||||
import {isBulkUpdateRecord, isUpdateRecord} from 'app/common/DocActions';
|
||||
import {getSetMapValue} from 'app/common/gutil';
|
||||
|
||||
export class ColumnCache<T> {
|
||||
private _cachedColIndexes = new Map<string, T>();
|
||||
|
||||
constructor(private _tableData: TableData) {
|
||||
// Whenever a table action is applied, consider invalidating per-column caches.
|
||||
this._tableData.tableActionEmitter.addListener(this._invalidateCache, this);
|
||||
this._tableData.dataLoadedEmitter.addListener(this._clearCache, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cached value for the given column, or calculates and caches the value using the
|
||||
* provided calc() function.
|
||||
*/
|
||||
public getValue(colId: string, calc: () => T): T {
|
||||
return getSetMapValue(this._cachedColIndexes, colId, calc);
|
||||
}
|
||||
|
||||
private _invalidateCache(action: DocAction): void {
|
||||
if (isUpdateRecord(action) || isBulkUpdateRecord(action)) {
|
||||
// If the update only affects existing records, only invalidate affected columns.
|
||||
const colValues = action[3];
|
||||
for (const colId of Object.keys(colValues)) {
|
||||
this._cachedColIndexes.delete(colId);
|
||||
}
|
||||
} else {
|
||||
// For add/delete actions and all schema changes, drop the cache entirely to be on the safe side.
|
||||
this._clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
private _clearCache(): void {
|
||||
this._cachedColIndexes.clear();
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,11 @@
|
||||
* TableData maintains a single table's data.
|
||||
*/
|
||||
import {ColumnACIndexes} from 'app/client/models/ColumnACIndexes';
|
||||
import {ColumnCache} from 'app/client/models/ColumnCache';
|
||||
import {DocData} from 'app/client/models/DocData';
|
||||
import {DocAction, ReplaceTableData, TableDataAction, UserAction} from 'app/common/DocActions';
|
||||
import {isRaisedException} from 'app/common/gristTypes';
|
||||
import {countIf} from 'app/common/gutil';
|
||||
import {ColTypeMap, TableData as BaseTableData} from 'app/common/TableData';
|
||||
import {BaseFormatter} from 'app/common/ValueFormatter';
|
||||
import {Emitter} from 'grainjs';
|
||||
@@ -19,6 +22,8 @@ export class TableData extends BaseTableData {
|
||||
|
||||
public readonly columnACIndexes = new ColumnACIndexes(this);
|
||||
|
||||
private _columnErrorCounts = new ColumnCache<number|undefined>(this);
|
||||
|
||||
/**
|
||||
* Constructor for TableData.
|
||||
* @param {DocData} docData: The root DocData object for this document.
|
||||
@@ -92,6 +97,17 @@ export class TableData extends BaseTableData {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts and returns the number of error values in the given column. The count is cached to
|
||||
* keep it faster for large tables, and the cache is cleared as needed on changes to the table.
|
||||
*/
|
||||
public countErrors(colId: string): number|undefined {
|
||||
return this._columnErrorCounts.getValue(colId, () => {
|
||||
const values = this.getColValues(colId);
|
||||
return values && countIf(values, isRaisedException);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an array of table-specific action to the server to be applied. The tableId should be
|
||||
* omitted from each `action` parameter and will be inserted automatically.
|
||||
|
||||
Reference in New Issue
Block a user