mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Configure more comprehensive eslint rules for Typescript
Summary: - Update rules to be more like we've had with tslint - Switch tsserver plugin to eslint (tsserver makes for a much faster way to lint in editors) - Apply suggested auto-fixes - Fix all lint errors and warnings in core/, app/, test/ Test Plan: Some behavior may change subtly (e.g. added missing awaits), relying on existing tests to catch problems. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2785
This commit is contained in:
parent
91fdef58ac
commit
526b0ad33e
@ -241,9 +241,9 @@ AceEditor.prototype._getContentHeight = function() {
|
|||||||
|
|
||||||
|
|
||||||
let _RangeConstructor = null; //singleton, load it lazily
|
let _RangeConstructor = null; //singleton, load it lazily
|
||||||
AceEditor.makeRange = function(a,b,c,d) {
|
AceEditor.makeRange = function(a, b, c, d) {
|
||||||
_RangeConstructor = _RangeConstructor || ace.acequire('ace/range').Range;
|
_RangeConstructor = _RangeConstructor || ace.acequire('ace/range').Range;
|
||||||
return new _RangeConstructor(a,b,c,d);
|
return new _RangeConstructor(a, b, c, d);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = AceEditor;
|
module.exports = AceEditor;
|
||||||
|
@ -262,7 +262,7 @@ export class ActionLog extends dispose.Disposable implements IDomComponent {
|
|||||||
if (this._loaded || !this._gristDoc) { return; }
|
if (this._loaded || !this._gristDoc) { return; }
|
||||||
this._loading(true);
|
this._loading(true);
|
||||||
// Returned actions are ordered with earliest actions first.
|
// Returned actions are ordered with earliest actions first.
|
||||||
const result = await this._gristDoc!.docComm.getActionSummaries();
|
const result = await this._gristDoc.docComm.getActionSummaries();
|
||||||
this._loading(false);
|
this._loading(false);
|
||||||
this._loaded = true;
|
this._loaded = true;
|
||||||
// Add the actions to our action log.
|
// Add the actions to our action log.
|
||||||
|
@ -15,7 +15,7 @@ import * as ko from 'knockout';
|
|||||||
import noop = require('lodash/noop');
|
import noop = require('lodash/noop');
|
||||||
|
|
||||||
// To simplify diff (avoid rearranging methods to satisfy private/public order).
|
// To simplify diff (avoid rearranging methods to satisfy private/public order).
|
||||||
// tslint:disable:member-ordering
|
/* eslint-disable @typescript-eslint/member-ordering */
|
||||||
|
|
||||||
type AceEditor = any;
|
type AceEditor = any;
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ export class ColumnTransform extends Disposable {
|
|||||||
this.transformColumn = this.field.column();
|
this.transformColumn = this.field.column();
|
||||||
this.transformColumn.origColRef(this.origColumn.getRowId());
|
this.transformColumn.origColRef(this.origColumn.getRowId());
|
||||||
this._setTransforming(true);
|
this._setTransforming(true);
|
||||||
return await this.postAddTransformColumn();
|
return this.postAddTransformColumn();
|
||||||
} finally {
|
} finally {
|
||||||
this.isCallPending(false);
|
this.isCallPending(false);
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ export class ColumnTransform extends Disposable {
|
|||||||
/**
|
/**
|
||||||
* A derived class can override to do some processing after this.transformColumn has been set.
|
* A derived class can override to do some processing after this.transformColumn has been set.
|
||||||
*/
|
*/
|
||||||
protected postAddTransformColumn() {
|
protected postAddTransformColumn(): void {
|
||||||
// Nothing in base class.
|
// Nothing in base class.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +207,7 @@ export class ColumnTransform extends Disposable {
|
|||||||
} finally {
|
} finally {
|
||||||
// Wait until the change completed to set column back, to avoid value flickering.
|
// Wait until the change completed to set column back, to avoid value flickering.
|
||||||
field.colRef(origRef);
|
field.colRef(origRef);
|
||||||
tableData.sendTableAction(['RemoveColumn', transformColId]);
|
void tableData.sendTableAction(['RemoveColumn', transformColId]);
|
||||||
this.dispose();
|
this.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -350,11 +350,11 @@ GridView.prototype.fillSelectionDown = function() {
|
|||||||
}).filter(colId => colId);
|
}).filter(colId => colId);
|
||||||
|
|
||||||
var colInfo = _.object(colIds, colIds.map(colId => {
|
var colInfo = _.object(colIds, colIds.map(colId => {
|
||||||
var val = this.tableModel.tableData.getValue(rowIds[0],colId);
|
var val = this.tableModel.tableData.getValue(rowIds[0], colId);
|
||||||
return rowIds.map(() => val);
|
return rowIds.map(() => val);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.tableModel.sendTableAction(["BulkUpdateRecord",rowIds,colInfo]);
|
this.tableModel.sendTableAction(["BulkUpdateRecord", rowIds, colInfo]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ export class Importer extends Disposable {
|
|||||||
} else if (item.kind === "url") {
|
} else if (item.kind === "url") {
|
||||||
uploadResult = await fetchURL(this._docComm, item.url);
|
uploadResult = await fetchURL(this._docComm, item.url);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Import source of kind ${item!.kind} are not yet supported!`);
|
throw new Error(`Import source of kind ${(item as any).kind} are not yet supported!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -391,7 +391,7 @@ export class Importer extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getSourceDescription(sourceInfo: SourceInfo, upload: UploadResult) {
|
function getSourceDescription(sourceInfo: SourceInfo, upload: UploadResult) {
|
||||||
const origName = upload!.files[sourceInfo.uploadFileIndex].origName;
|
const origName = upload.files[sourceInfo.uploadFileIndex].origName;
|
||||||
return sourceInfo.origTableName ? origName + ' - ' + sourceInfo.origTableName : origName;
|
return sourceInfo.origTableName ? origName + ' - ' + sourceInfo.origTableName : origName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ export function makeSearchToolbarGroup(gristDoc: GristDoc) {
|
|||||||
// Active normally.
|
// Active normally.
|
||||||
const commandGroup = createGroup({
|
const commandGroup = createGroup({
|
||||||
find: () => { input.focus(); },
|
find: () => { input.focus(); },
|
||||||
findNext: () => { searcher.findNext(); }, // tslint:disable-line:no-floating-promises TODO
|
findNext: () => { searcher.findNext(); }, // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||||
findPrev: () => { searcher.findPrev(); }, // tslint:disable-line:no-floating-promises TODO
|
findPrev: () => { searcher.findPrev(); }, // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||||
}, null, true);
|
}, null, true);
|
||||||
|
|
||||||
// Return an array of one item (for a toolbar group of a single item). The item is an array of
|
// Return an array of one item (for a toolbar group of a single item). The item is an array of
|
||||||
@ -49,7 +49,7 @@ export function makeSearchToolbarGroup(gristDoc: GristDoc) {
|
|||||||
// the searchbox is created so early that the actions like accept/cancel get overridden).
|
// the searchbox is created so early that the actions like accept/cancel get overridden).
|
||||||
dom.on('keydown', (e: KeyboardEvent) => {
|
dom.on('keydown', (e: KeyboardEvent) => {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 13: searcher.findNext(); break; // tslint:disable-line:no-floating-promises TODO
|
case 13: searcher.findNext(); break; // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||||
case 27: input.blur(); break;
|
case 27: input.blur(); break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -264,7 +264,7 @@ CellSelector.prototype.rowCount = function() {
|
|||||||
return this.rowUpper() - this.rowLower() + 1;
|
return this.rowUpper() - this.rowLower() + 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
CellSelector.prototype.selectArea = function(rowStartIdx,colStartIdx,rowEndIdx,colEndIdx) {
|
CellSelector.prototype.selectArea = function(rowStartIdx, colStartIdx, rowEndIdx, colEndIdx) {
|
||||||
this.row.start(rowStartIdx);
|
this.row.start(rowStartIdx);
|
||||||
this.col.start(colStartIdx);
|
this.col.start(colStartIdx);
|
||||||
this.row.end(rowEndIdx);
|
this.row.end(rowEndIdx);
|
||||||
|
@ -22,7 +22,7 @@ import isEmpty = require('lodash/isEmpty');
|
|||||||
import pickBy = require('lodash/pickBy');
|
import pickBy = require('lodash/pickBy');
|
||||||
|
|
||||||
// To simplify diff (avoid rearranging methods to satisfy private/public order).
|
// To simplify diff (avoid rearranging methods to satisfy private/public order).
|
||||||
// tslint:disable:member-ordering
|
/* eslint-disable @typescript-eslint/member-ordering */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of TypeTransform for a single field. Extends ColumnTransform.
|
* Creates an instance of TypeTransform for a single field. Extends ColumnTransform.
|
||||||
|
@ -58,7 +58,7 @@ function ViewConfigTab(options) {
|
|||||||
}) || self.viewSectionData.at(0);
|
}) || self.viewSectionData.at(0);
|
||||||
}));
|
}));
|
||||||
this.isDetail = this.autoDispose(ko.computed(function() {
|
this.isDetail = this.autoDispose(ko.computed(function() {
|
||||||
return ['detail','single'].includes(this.viewModel.activeSection().parentKey());
|
return ['detail', 'single'].includes(this.viewModel.activeSection().parentKey());
|
||||||
}, this));
|
}, this));
|
||||||
this.isChart = this.autoDispose(ko.computed(function() {
|
this.isChart = this.autoDispose(ko.computed(function() {
|
||||||
return this.viewModel.activeSection().parentKey() === 'chart';}, this));
|
return this.viewModel.activeSection().parentKey() === 'chart';}, this));
|
||||||
|
10
app/client/declarations.d.ts
vendored
10
app/client/declarations.d.ts
vendored
@ -35,7 +35,6 @@ declare module "app/client/components/BaseView" {
|
|||||||
import {Cursor, CursorPos} from 'app/client/components/Cursor';
|
import {Cursor, CursorPos} from 'app/client/components/Cursor';
|
||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import {Disposable} from 'app/client/lib/dispose';
|
import {Disposable} from 'app/client/lib/dispose';
|
||||||
import {KoArray} from "app/client/lib/koArray";
|
|
||||||
import * as BaseRowModel from "app/client/models/BaseRowModel";
|
import * as BaseRowModel from "app/client/models/BaseRowModel";
|
||||||
import {DataRowModel} from 'app/client/models/DataRowModel';
|
import {DataRowModel} from 'app/client/models/DataRowModel';
|
||||||
import {LazyArrayModel} from "app/client/models/DataTableModel";
|
import {LazyArrayModel} from "app/client/models/DataTableModel";
|
||||||
@ -72,10 +71,8 @@ declare module "app/client/components/BaseView" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module "app/client/components/RefSelect" {
|
declare module "app/client/components/RefSelect" {
|
||||||
import {GristDoc, TabContent} from 'app/client/components/GristDoc';
|
|
||||||
import {Disposable} from 'app/client/lib/dispose';
|
import {Disposable} from 'app/client/lib/dispose';
|
||||||
import {ColumnRec} from "app/client/models/DocModel";
|
import {ColumnRec} from "app/client/models/DocModel";
|
||||||
import {DomArg} from 'grainjs';
|
|
||||||
import {DocModel} from "app/client/models/DocModel";
|
import {DocModel} from "app/client/models/DocModel";
|
||||||
import {FieldBuilder} from "app/client/widgets/FieldBuilder";
|
import {FieldBuilder} from "app/client/widgets/FieldBuilder";
|
||||||
|
|
||||||
@ -161,11 +158,11 @@ declare module "app/client/models/BaseRowModel" {
|
|||||||
class BaseRowModel extends Disposable {
|
class BaseRowModel extends Disposable {
|
||||||
public id: ko.Computed<number>;
|
public id: ko.Computed<number>;
|
||||||
public _index: ko.Observable<number|null>;
|
public _index: ko.Observable<number|null>;
|
||||||
public getRowId(): number;
|
|
||||||
public updateColValues(colValues: ColValues): Promise<void>;
|
|
||||||
public _table: TableModel;
|
public _table: TableModel;
|
||||||
protected _rowId: number | 'new' | null;
|
protected _rowId: number | 'new' | null;
|
||||||
protected _fields: string[];
|
protected _fields: string[];
|
||||||
|
public getRowId(): number;
|
||||||
|
public updateColValues(colValues: ColValues): Promise<void>;
|
||||||
}
|
}
|
||||||
export = BaseRowModel;
|
export = BaseRowModel;
|
||||||
}
|
}
|
||||||
@ -286,10 +283,9 @@ declare module "app/client/models/DataTableModel" {
|
|||||||
import * as BaseRowModel from "app/client/models/BaseRowModel";
|
import * as BaseRowModel from "app/client/models/BaseRowModel";
|
||||||
import {DocModel, TableRec} from "app/client/models/DocModel";
|
import {DocModel, TableRec} from "app/client/models/DocModel";
|
||||||
import {TableQuerySets} from 'app/client/models/QuerySet';
|
import {TableQuerySets} from 'app/client/models/QuerySet';
|
||||||
import {RowSource, SortedRowSet} from "app/client/models/rowset";
|
import {SortedRowSet} from "app/client/models/rowset";
|
||||||
import {TableData} from "app/client/models/TableData";
|
import {TableData} from "app/client/models/TableData";
|
||||||
import * as TableModel from "app/client/models/TableModel";
|
import * as TableModel from "app/client/models/TableModel";
|
||||||
import {CellValue} from "app/common/DocActions";
|
|
||||||
|
|
||||||
namespace DataTableModel {
|
namespace DataTableModel {
|
||||||
interface LazyArrayModel<T> extends KoArray<T | null> {
|
interface LazyArrayModel<T> extends KoArray<T | null> {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Implements an autocomplete dropdown.
|
* Implements an autocomplete dropdown.
|
||||||
*/
|
*/
|
||||||
import {createPopper, Instance as Popper, Modifier, Options as PopperOptions} from '@popperjs/core';
|
import {createPopper, Modifier, Instance as Popper, Options as PopperOptions} from '@popperjs/core';
|
||||||
import {ACItem, ACResults, HighlightFunc} from 'app/client/lib/ACIndex';
|
import {ACItem, ACResults, HighlightFunc} from 'app/client/lib/ACIndex';
|
||||||
import {reportError} from 'app/client/models/errors';
|
import {reportError} from 'app/client/models/errors';
|
||||||
import {Disposable, dom, EventCB, IDisposable} from 'grainjs';
|
import {Disposable, dom, EventCB, IDisposable} from 'grainjs';
|
||||||
|
@ -174,7 +174,7 @@ export class BillingModelImpl extends Disposable implements BillingModel {
|
|||||||
await this._billingAPI.updateAddress(newAddr || undefined, newSettings || undefined);
|
await this._billingAPI.updateAddress(newAddr || undefined, newSettings || undefined);
|
||||||
}
|
}
|
||||||
// If there is an org update, re-initialize the org in the client.
|
// If there is an org update, re-initialize the org in the client.
|
||||||
if (newSettings) { await this._appModel.topAppModel.initialize(); }
|
if (newSettings) { this._appModel.topAppModel.initialize(); }
|
||||||
} else {
|
} else {
|
||||||
throw new Error('BillingPage _submit error: no task in progress');
|
throw new Error('BillingPage _submit error: no task in progress');
|
||||||
}
|
}
|
||||||
|
@ -27,13 +27,6 @@ const ROW_ID_SKIP = -1;
|
|||||||
* This should be the only part of the code that knows that.
|
* This should be the only part of the code that knows that.
|
||||||
*/
|
*/
|
||||||
export class ExtraRows {
|
export class ExtraRows {
|
||||||
readonly leftTableDelta?: TableDelta;
|
|
||||||
readonly rightTableDelta?: TableDelta;
|
|
||||||
readonly rightAddRows: Set<number>;
|
|
||||||
readonly rightRemoveRows: Set<number>;
|
|
||||||
readonly leftAddRows: Set<number>;
|
|
||||||
readonly leftRemoveRows: Set<number>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map back from a possibly synthetic row id to an original strictly-positive row id.
|
* Map back from a possibly synthetic row id to an original strictly-positive row id.
|
||||||
*/
|
*/
|
||||||
@ -44,7 +37,14 @@ export class ExtraRows {
|
|||||||
return { type: 'local-remove', id: -(rowId + 2) / 2 };
|
return { type: 'local-remove', id: -(rowId + 2) / 2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructor(readonly tableId: string, readonly comparison?: DocStateComparisonDetails) {
|
public readonly leftTableDelta?: TableDelta;
|
||||||
|
public readonly rightTableDelta?: TableDelta;
|
||||||
|
public readonly rightAddRows: Set<number>;
|
||||||
|
public readonly rightRemoveRows: Set<number>;
|
||||||
|
public readonly leftAddRows: Set<number>;
|
||||||
|
public readonly leftRemoveRows: Set<number>;
|
||||||
|
|
||||||
|
public constructor(public readonly tableId: string, public readonly comparison?: DocStateComparisonDetails) {
|
||||||
const remoteTableId = getRemoteTableId(tableId, comparison);
|
const remoteTableId = getRemoteTableId(tableId, comparison);
|
||||||
this.leftTableDelta = this.comparison?.leftChanges?.tableDeltas[tableId];
|
this.leftTableDelta = this.comparison?.leftChanges?.tableDeltas[tableId];
|
||||||
if (remoteTableId) {
|
if (remoteTableId) {
|
||||||
|
@ -100,7 +100,7 @@ export class DocData extends BaseDocData {
|
|||||||
this._nextDesc = options.description;
|
this._nextDesc = options.description;
|
||||||
this._lastActionNum = null;
|
this._lastActionNum = null;
|
||||||
this._triggerBundleFinalize = triggerFinalize;
|
this._triggerBundleFinalize = triggerFinalize;
|
||||||
await prepareResolve(options.prepare());
|
prepareResolve(options.prepare());
|
||||||
this._shouldIncludeInBundle = options.shouldIncludeInBundle;
|
this._shouldIncludeInBundle = options.shouldIncludeInBundle;
|
||||||
|
|
||||||
await triggerFinalizePromise;
|
await triggerFinalizePromise;
|
||||||
@ -162,7 +162,7 @@ export class DocData extends BaseDocData {
|
|||||||
public sendActions(actions: UserAction[], optDesc?: string): Promise<any[]> {
|
public sendActions(actions: UserAction[], optDesc?: string): Promise<any[]> {
|
||||||
// Some old code relies on this promise being a bluebird Promise.
|
// Some old code relies on this promise being a bluebird Promise.
|
||||||
// TODO Remove bluebird and this cast.
|
// TODO Remove bluebird and this cast.
|
||||||
return bluebird.Promise.resolve(this._sendActionsImpl(actions, optDesc)) as any;
|
return bluebird.Promise.resolve(this._sendActionsImpl(actions, optDesc)) as unknown as Promise<any[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,7 +94,8 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
|||||||
public readonly isReadonly = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isReadonly : false);
|
public readonly isReadonly = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isReadonly : false);
|
||||||
public readonly isPrefork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isPreFork : false);
|
public readonly isPrefork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isPreFork : false);
|
||||||
public readonly isFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isFork : false);
|
public readonly isFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isFork : false);
|
||||||
public readonly isRecoveryMode = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isRecoveryMode : false);
|
public readonly isRecoveryMode = Computed.create(this, this.currentDoc,
|
||||||
|
(use, doc) => doc ? doc.isRecoveryMode : false);
|
||||||
public readonly userOverride = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.userOverride : null);
|
public readonly userOverride = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.userOverride : null);
|
||||||
public readonly isBareFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isBareFork : false);
|
public readonly isBareFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isBareFork : false);
|
||||||
public readonly isSample = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isSample : false);
|
public readonly isSample = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isSample : false);
|
||||||
@ -133,8 +134,9 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
|||||||
if (!urlId) {
|
if (!urlId) {
|
||||||
this._openerHolder.clear();
|
this._openerHolder.clear();
|
||||||
} else {
|
} else {
|
||||||
FlowRunner.create(this._openerHolder, (flow: AsyncFlow) => this._openDoc(flow, urlId, urlOpenMode,
|
FlowRunner.create(this._openerHolder,
|
||||||
state.params?.compare, linkParameters))
|
(flow: AsyncFlow) => this._openDoc(flow, urlId, urlOpenMode, state.params?.compare, linkParameters)
|
||||||
|
)
|
||||||
.resultPromise.catch(err => this._onOpenError(err));
|
.resultPromise.catch(err => this._onOpenError(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,8 +307,10 @@ function addMenu(importSources: ImportSource[], gristDoc: GristDoc, isReadonly:
|
|||||||
menuIcon("Widget"), "Add Widget to Page", testId('dp-add-widget-to-page'),
|
menuIcon("Widget"), "Add Widget to Page", testId('dp-add-widget-to-page'),
|
||||||
dom.cls('disabled', isReadonly)
|
dom.cls('disabled', isReadonly)
|
||||||
),
|
),
|
||||||
menuItem(() => gristDoc.addEmptyTable().catch(reportError), menuIcon("TypeTable"), "Add Empty Table", testId('dp-empty-table'),
|
menuItem(() => gristDoc.addEmptyTable().catch(reportError),
|
||||||
dom.cls('disabled', isReadonly)),
|
menuIcon("TypeTable"), "Add Empty Table", testId('dp-empty-table'),
|
||||||
|
dom.cls('disabled', isReadonly)
|
||||||
|
),
|
||||||
menuDivider(),
|
menuDivider(),
|
||||||
...importSources.map((importSource, i) =>
|
...importSources.map((importSource, i) =>
|
||||||
menuItem(importSource.action,
|
menuItem(importSource.action,
|
||||||
|
@ -314,7 +314,7 @@ function convertQueryToRefs(docModel: DocModel, query: Query): QueryRefs {
|
|||||||
const tableRec: any = docModel.dataTables[query.tableId].tableMetaRow;
|
const tableRec: any = docModel.dataTables[query.tableId].tableMetaRow;
|
||||||
|
|
||||||
const colRefsByColId: {[colId: string]: number} = {};
|
const colRefsByColId: {[colId: string]: number} = {};
|
||||||
for (const col of (tableRec as any).columns.peek().peek()) {
|
for (const col of tableRec.columns.peek().peek()) {
|
||||||
colRefsByColId[col.colId.peek()] = col.getRowId();
|
colRefsByColId[col.colId.peek()] = col.getRowId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ export class SearchModelImpl extends Disposable implements SearchModel {
|
|||||||
|
|
||||||
// Listen to input value changes (debounced) to activate searching.
|
// Listen to input value changes (debounced) to activate searching.
|
||||||
const findFirst = debounce((_value: string) => this._findFirst(_value), 100);
|
const findFirst = debounce((_value: string) => this._findFirst(_value), 100);
|
||||||
this.autoDispose(this.value.addListener(v => { findFirst(v); }));
|
this.autoDispose(this.value.addListener(v => { void findFirst(v); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findNext() {
|
public async findNext() {
|
||||||
|
@ -7,7 +7,7 @@ import {DocData} from 'app/client/models/DocData';
|
|||||||
import {DocAction, ReplaceTableData, TableDataAction, UserAction} from 'app/common/DocActions';
|
import {DocAction, ReplaceTableData, TableDataAction, UserAction} from 'app/common/DocActions';
|
||||||
import {isRaisedException} from 'app/common/gristTypes';
|
import {isRaisedException} from 'app/common/gristTypes';
|
||||||
import {countIf} from 'app/common/gutil';
|
import {countIf} from 'app/common/gutil';
|
||||||
import {ColTypeMap, TableData as BaseTableData} from 'app/common/TableData';
|
import {TableData as BaseTableData, ColTypeMap} from 'app/common/TableData';
|
||||||
import {BaseFormatter} from 'app/common/ValueFormatter';
|
import {BaseFormatter} from 'app/common/ValueFormatter';
|
||||||
import {Emitter} from 'grainjs';
|
import {Emitter} from 'grainjs';
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ class BillingPaymentForm extends BillingSubForm {
|
|||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
const autofill = this._options.autofill;
|
const autofill = this._options.autofill;
|
||||||
const stripeAPIKey = (G.window as any).gristConfig.stripeAPIKey;
|
const stripeAPIKey = G.window.gristConfig.stripeAPIKey;
|
||||||
try {
|
try {
|
||||||
this._stripe = G.Stripe(stripeAPIKey);
|
this._stripe = G.Stripe(stripeAPIKey);
|
||||||
this._elements = this._stripe.elements();
|
this._elements = this._stripe.elements();
|
||||||
@ -462,7 +462,7 @@ function checkRequired(propertyName: string) {
|
|||||||
// if the current observable value is valid.
|
// if the current observable value is valid.
|
||||||
function createValidated(
|
function createValidated(
|
||||||
owner: IDisposableOwnerT<any>,
|
owner: IDisposableOwnerT<any>,
|
||||||
checkValidity: (value: string) => void
|
checkValidity: (value: string) => void|Promise<void>,
|
||||||
): IValidated<string> {
|
): IValidated<string> {
|
||||||
const value = Observable.create(owner, '');
|
const value = Observable.create(owner, '');
|
||||||
const isInvalid = Observable.create<boolean>(owner, false);
|
const isInvalid = Observable.create<boolean>(owner, false);
|
||||||
|
@ -352,7 +352,7 @@ function getFieldNewPosition(fields: KoArray<ViewFieldRec>, item: IField,
|
|||||||
return tableUtil.fieldInsertPositions(fields, index, 1)[0];
|
return tableUtil.fieldInsertPositions(fields, index, 1)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemIndex<T>(collection: KoArray<ViewFieldRec>, item: ViewFieldRec|null): number {
|
function getItemIndex(collection: KoArray<ViewFieldRec>, item: ViewFieldRec|null): number {
|
||||||
if (item !== null) {
|
if (item !== null) {
|
||||||
return collection.peek().indexOf(item);
|
return collection.peek().indexOf(item);
|
||||||
}
|
}
|
||||||
|
@ -194,8 +194,8 @@ export const cssHideForNarrowScreen = styled('div', `
|
|||||||
* Attaches the global css properties to the document's root to them available in the page.
|
* Attaches the global css properties to the document's root to them available in the page.
|
||||||
*/
|
*/
|
||||||
export function attachCssRootVars(productFlavor: ProductFlavor, varsOnly: boolean = false) {
|
export function attachCssRootVars(productFlavor: ProductFlavor, varsOnly: boolean = false) {
|
||||||
dom.update(document.documentElement!, varsOnly ? dom.cls(cssVarsOnly.className) : dom.cls(cssRootVars));
|
dom.update(document.documentElement, varsOnly ? dom.cls(cssVarsOnly.className) : dom.cls(cssRootVars));
|
||||||
document.documentElement!.classList.add(cssRoot.className);
|
document.documentElement.classList.add(cssRoot.className);
|
||||||
document.body.classList.add(cssBody.className);
|
document.body.classList.add(cssBody.className);
|
||||||
const theme = getTheme(productFlavor);
|
const theme = getTheme(productFlavor);
|
||||||
if (theme.bodyClassName) {
|
if (theme.bodyClassName) {
|
||||||
|
@ -4,7 +4,7 @@ import { CellValue } from 'app/common/DocActions';
|
|||||||
import { isVersions } from 'app/common/gristTypes';
|
import { isVersions } from 'app/common/gristTypes';
|
||||||
import { inlineStyle } from 'app/common/gutil';
|
import { inlineStyle } from 'app/common/gutil';
|
||||||
import { BaseFormatter } from 'app/common/ValueFormatter';
|
import { BaseFormatter } from 'app/common/ValueFormatter';
|
||||||
import { Diff, DIFF_DELETE, DIFF_INSERT, diff_match_patch as DiffMatchPatch, DIFF_EQUAL } from 'diff-match-patch';
|
import { Diff, DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, diff_match_patch as DiffMatchPatch } from 'diff-match-patch';
|
||||||
import { Computed, dom } from 'grainjs';
|
import { Computed, dom } from 'grainjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,7 +27,7 @@ import * as gristTypes from 'app/common/gristTypes';
|
|||||||
import * as gutil from 'app/common/gutil';
|
import * as gutil from 'app/common/gutil';
|
||||||
import { CellValue } from 'app/plugin/GristData';
|
import { CellValue } from 'app/plugin/GristData';
|
||||||
import { delay } from 'bluebird';
|
import { delay } from 'bluebird';
|
||||||
import { Computed, Disposable, dom as grainjsDom, fromKo, Holder, IDisposable, makeTestId } from 'grainjs';
|
import { Computed, Disposable, fromKo, dom as grainjsDom, Holder, IDisposable, makeTestId } from 'grainjs';
|
||||||
import * as ko from 'knockout';
|
import * as ko from 'knockout';
|
||||||
import * as _ from 'underscore';
|
import * as _ from 'underscore';
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ export class FieldBuilder extends Disposable {
|
|||||||
private readonly widgetCons: ko.Computed<{create: (...args: any[]) => NewAbstractWidget}>;
|
private readonly widgetCons: ko.Computed<{create: (...args: any[]) => NewAbstractWidget}>;
|
||||||
private readonly docModel: DocModel;
|
private readonly docModel: DocModel;
|
||||||
|
|
||||||
public constructor(readonly gristDoc: GristDoc, readonly field: ViewFieldRec,
|
public constructor(public readonly gristDoc: GristDoc, public readonly field: ViewFieldRec,
|
||||||
private _cursor: Cursor) {
|
private _cursor: Cursor) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ function readAclRules(docData: DocData, {log, compile}: ReadAclOptions): ReadAcl
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const [resourceId, rules] of rulesByResource.entries()) {
|
for (const [resourceId, rules] of rulesByResource.entries()) {
|
||||||
const resourceRec = resourcesTable.getRecord(resourceId as number);
|
const resourceRec = resourcesTable.getRecord(resourceId);
|
||||||
if (!resourceRec) {
|
if (!resourceRec) {
|
||||||
throw new Error(`ACLRule ${rules[0].id} refers to an invalid ACLResource ${resourceId}`);
|
throw new Error(`ACLRule ${rules[0].id} refers to an invalid ACLResource ${resourceId}`);
|
||||||
continue;
|
continue;
|
||||||
|
@ -82,7 +82,8 @@ const SCHEMA_ACTIONS = new Set(['AddTable', 'RemoveTable', 'RenameTable', 'AddCo
|
|||||||
/**
|
/**
|
||||||
* Determines whether a given action is a schema action or not.
|
* Determines whether a given action is a schema action or not.
|
||||||
*/
|
*/
|
||||||
export function isSchemaAction(action: DocAction): action is AddTable | RemoveTable | RenameTable | AddColumn | RemoveColumn | RenameColumn | ModifyColumn {
|
export function isSchemaAction(action: DocAction):
|
||||||
|
action is AddTable | RemoveTable | RenameTable | AddColumn | RemoveColumn | RenameColumn | ModifyColumn {
|
||||||
return SCHEMA_ACTIONS.has(action[0]);
|
return SCHEMA_ACTIONS.has(action[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ export interface OpenLocalDocResult {
|
|||||||
userOverride?: UserOverride;
|
userOverride?: UserOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserOverride {
|
export interface UserOverride {
|
||||||
user: FullUser|null;
|
user: FullUser|null;
|
||||||
access: Role|null;
|
access: Role|null;
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ export abstract class BaseComponent implements IForwarderDest {
|
|||||||
public async forwardMessage(msg: IMsgCustom): Promise<any> {
|
public async forwardMessage(msg: IMsgCustom): Promise<any> {
|
||||||
if (!this._activated) { await this.activate(); }
|
if (!this._activated) { await this.activate(); }
|
||||||
this.inactivityTimer.ping();
|
this.inactivityTimer.ping();
|
||||||
this.doForwardMessage(msg); // tslint:disable-line:no-floating-promises TODO
|
this.doForwardMessage(msg); // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract doForwardCall(c: IMsgRpcCall): Promise<any>;
|
protected abstract doForwardCall(c: IMsgRpcCall): Promise<any>;
|
||||||
|
@ -629,7 +629,7 @@ export class UserAPIImpl extends BaseAPI implements UserAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class DocWorkerAPIImpl extends BaseAPI implements DocWorkerAPI {
|
export class DocWorkerAPIImpl extends BaseAPI implements DocWorkerAPI {
|
||||||
constructor(readonly url: string, _options: IOptions = {}) {
|
constructor(public readonly url: string, _options: IOptions = {}) {
|
||||||
super(_options);
|
super(_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -682,7 +682,7 @@ export class DocWorkerAPIImpl extends BaseAPI implements DocWorkerAPI {
|
|||||||
export class DocAPIImpl extends BaseAPI implements DocAPI {
|
export class DocAPIImpl extends BaseAPI implements DocAPI {
|
||||||
private _url: string;
|
private _url: string;
|
||||||
|
|
||||||
constructor(url: string, readonly docId: string, options: IOptions = {}) {
|
constructor(url: string, public readonly docId: string, options: IOptions = {}) {
|
||||||
super(options);
|
super(options);
|
||||||
this._url = `${url}/api/docs/${docId}`;
|
this._url = `${url}/api/docs/${docId}`;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
export function tbind<T, R, Args extends any[]>(func: (this: T, ...a: Args) => R, context: T): (...a: Args) => R;
|
export function tbind<T, R, Args extends any[]>(func: (this: T, ...a: Args) => R, context: T): (...a: Args) => R;
|
||||||
|
|
||||||
// Bind context and first arg for a function of up to 5 args.
|
// Bind context and first arg for a function of up to 5 args.
|
||||||
export function tbind<T, R, X, Args extends any[]>(func: (this: T, x: X, ...a: Args) => R, context: T, x: X): (...a: Args) => R;
|
export function tbind<T, R, X, Args extends any[]>(
|
||||||
|
func: (this: T, x: X, ...a: Args) => R, context: T, x: X
|
||||||
|
): (...a: Args) => R;
|
||||||
|
|
||||||
export function tbind(func: any, context: any, ...boundArgs: any[]): any {
|
export function tbind(func: any, context: any, ...boundArgs: any[]): any {
|
||||||
return func.bind(context, ...boundArgs);
|
return func.bind(context, ...boundArgs);
|
||||||
|
@ -12,7 +12,8 @@ type PromisifiedFunction<T extends AnyFunction> =
|
|||||||
T extends (a1: infer A1) => infer U ? (a1: A1) => Promise<Unpacked<U>> :
|
T extends (a1: infer A1) => infer U ? (a1: A1) => Promise<Unpacked<U>> :
|
||||||
T extends (a1: infer A1, a2: infer A2) => infer U ? (a1: A1, a2: A2) => Promise<Unpacked<U>> :
|
T extends (a1: infer A1, a2: infer A2) => infer U ? (a1: A1, a2: A2) => Promise<Unpacked<U>> :
|
||||||
T extends (a1: infer A1, a2: infer A2, a3: infer A3) => infer U ? (a1: A1, a2: A2, a3: A3) => Promise<Unpacked<U>> :
|
T extends (a1: infer A1, a2: infer A2, a3: infer A3) => infer U ? (a1: A1, a2: A2, a3: A3) => Promise<Unpacked<U>> :
|
||||||
T extends (a1: infer A1, a2: infer A2, a3: infer A3, a4: infer A4) => infer U ? (a1: A1, a2: A2, a3: A3, a4: A4) => Promise<Unpacked<U>> :
|
T extends (a1: infer A1, a2: infer A2, a3: infer A3, a4: infer A4) =>
|
||||||
|
infer U ? (a1: A1, a2: A2, a3: A3, a4: A4) => Promise<Unpacked<U>> :
|
||||||
// ...
|
// ...
|
||||||
T extends (...args: any[]) => infer U ? (...args: any[]) => Promise<Unpacked<U>> : T;
|
T extends (...args: any[]) => infer U ? (...args: any[]) => Promise<Unpacked<U>> : T;
|
||||||
|
|
||||||
|
@ -55,7 +55,9 @@ export class DocApiForwarder {
|
|||||||
app.use('^/api/docs$', withoutDoc);
|
app.use('^/api/docs$', withoutDoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _forwardToDocWorker(withDocId: boolean, role: 'viewers'|null, req: express.Request, res: express.Response): Promise<void> {
|
private async _forwardToDocWorker(
|
||||||
|
withDocId: boolean, role: 'viewers'|null, req: express.Request, res: express.Response,
|
||||||
|
): Promise<void> {
|
||||||
let docId: string|null = null;
|
let docId: string|null = null;
|
||||||
if (withDocId) {
|
if (withDocId) {
|
||||||
const docAuth = await getOrSetDocAuth(req as RequestWithLogin, this._dbManager, req.params.docId);
|
const docAuth = await getOrSetDocAuth(req as RequestWithLogin, this._dbManager, req.params.docId);
|
||||||
|
@ -424,7 +424,7 @@ export class DocWorkerMap implements IDocWorkerMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateDocStatus(docId: string, checksum: string): Promise<void> {
|
public async updateDocStatus(docId: string, checksum: string): Promise<void> {
|
||||||
this.updateChecksum('doc', docId, checksum);
|
return this.updateChecksum('doc', docId, checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateChecksum(family: string, key: string, checksum: string) {
|
public async updateChecksum(family: string, key: string, checksum: string) {
|
||||||
|
@ -8,8 +8,8 @@ import {checkSubdomainValidity} from 'app/common/orgNameUtils';
|
|||||||
import * as roles from 'app/common/roles';
|
import * as roles from 'app/common/roles';
|
||||||
// TODO: API should implement UserAPI
|
// TODO: API should implement UserAPI
|
||||||
import {ANONYMOUS_USER_EMAIL, DocumentProperties, EVERYONE_EMAIL,
|
import {ANONYMOUS_USER_EMAIL, DocumentProperties, EVERYONE_EMAIL,
|
||||||
ManagerDelta, NEW_DOCUMENT_CODE, Organization as OrgInfo,
|
ManagerDelta, NEW_DOCUMENT_CODE, OrganizationProperties,
|
||||||
OrganizationProperties, PermissionData, PermissionDelta, SUPPORT_EMAIL, UserAccessData,
|
Organization as OrgInfo, PermissionData, PermissionDelta, SUPPORT_EMAIL, UserAccessData,
|
||||||
WorkspaceProperties} from "app/common/UserAPI";
|
WorkspaceProperties} from "app/common/UserAPI";
|
||||||
import {AclRule, AclRuleDoc, AclRuleOrg, AclRuleWs} from "app/gen-server/entity/AclRule";
|
import {AclRule, AclRuleDoc, AclRuleOrg, AclRuleWs} from "app/gen-server/entity/AclRule";
|
||||||
import {Alias} from "app/gen-server/entity/Alias";
|
import {Alias} from "app/gen-server/entity/Alias";
|
||||||
@ -1903,7 +1903,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
name: u.name,
|
name: u.name,
|
||||||
email: u.logins.map((login: Login) => login.displayEmail)[0],
|
email: u.logins.map((login: Login) => login.displayEmail)[0],
|
||||||
picture: u.picture,
|
picture: u.picture,
|
||||||
access: userRoleMap[u.id] as roles.Role
|
access: userRoleMap[u.id]
|
||||||
}));
|
}));
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -2811,7 +2811,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
let effectiveUserId = userId;
|
let effectiveUserId = userId;
|
||||||
let threshold = options.markPermissions;
|
let threshold = options.markPermissions;
|
||||||
if (options.allowSpecialPermit && scope.specialPermit && scope.specialPermit.docId) {
|
if (options.allowSpecialPermit && scope.specialPermit && scope.specialPermit.docId) {
|
||||||
query = query.andWhere('docs.id = :docId', {docId: scope.specialPermit.docId!});
|
query = query.andWhere('docs.id = :docId', {docId: scope.specialPermit.docId});
|
||||||
effectiveUserId = this.getPreviewerUserId();
|
effectiveUserId = this.getPreviewerUserId();
|
||||||
threshold = Permissions.VIEW;
|
threshold = Permissions.VIEW;
|
||||||
}
|
}
|
||||||
@ -3051,7 +3051,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
if (!groupProps.orgOnly || !inherit) {
|
if (!groupProps.orgOnly || !inherit) {
|
||||||
// Skip this group if it's an org only group and the resource inherits from a parent.
|
// Skip this group if it's an org only group and the resource inherits from a parent.
|
||||||
const group = new Group();
|
const group = new Group();
|
||||||
group.name = groupProps.name as roles.Role;
|
group.name = groupProps.name;
|
||||||
if (inherit) {
|
if (inherit) {
|
||||||
this._setInheritance(group, inherit);
|
this._setInheritance(group, inherit);
|
||||||
}
|
}
|
||||||
@ -3333,7 +3333,10 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
`${everyoneId} IN (gu0.user_id, gu1.user_id, gu2.user_id, gu3.user_id) ` +
|
`${everyoneId} IN (gu0.user_id, gu1.user_id, gu2.user_id, gu3.user_id) ` +
|
||||||
`then ${everyoneContribution} else (case when ` +
|
`then ${everyoneContribution} else (case when ` +
|
||||||
`${anonId} IN (gu0.user_id, gu1.user_id, gu2.user_id, gu3.user_id) ` +
|
`${anonId} IN (gu0.user_id, gu1.user_id, gu2.user_id, gu3.user_id) ` +
|
||||||
`then ${Permissions.PUBLIC} | acl_rules.permissions else acl_rules.permissions end) end)`, 8), 'permissions');
|
`then ${Permissions.PUBLIC} | acl_rules.permissions else acl_rules.permissions end) end)`, 8
|
||||||
|
),
|
||||||
|
'permissions'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
q = q.from('acl_rules', 'acl_rules');
|
q = q.from('acl_rules', 'acl_rules');
|
||||||
q = this._getUsersAcls(q, users, accessStyle);
|
q = this._getUsersAcls(q, users, accessStyle);
|
||||||
|
@ -210,7 +210,9 @@ export class Housekeeper {
|
|||||||
|
|
||||||
// Call a document endpoint with a permit, cleaning up after the call.
|
// Call a document endpoint with a permit, cleaning up after the call.
|
||||||
// Checks that the user is the support user.
|
// Checks that the user is the support user.
|
||||||
private _withSupport(callback: (docId: string, headers: Record<string, string>) => Promise<Fetch.Response>): express.RequestHandler {
|
private _withSupport(
|
||||||
|
callback: (docId: string, headers: Record<string, string>) => Promise<Fetch.Response>
|
||||||
|
): express.RequestHandler {
|
||||||
return expressWrap(async (req, res) => {
|
return expressWrap(async (req, res) => {
|
||||||
const userId = getAuthorizedUserId(req);
|
const userId = getAuthorizedUserId(req);
|
||||||
if (userId !== this._dbManager.getSupportUserId()) {
|
if (userId !== this._dbManager.getSupportUserId()) {
|
||||||
|
@ -133,7 +133,7 @@ export async function addImporter(name: string, path: string, mode: 'fullscreen'
|
|||||||
*/
|
*/
|
||||||
export function ready(): void {
|
export function ready(): void {
|
||||||
rpc.processIncoming();
|
rpc.processIncoming();
|
||||||
rpc.sendReadyMessage();
|
void rpc.sendReadyMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPluginPath(location: Location) {
|
function getPluginPath(location: Location) {
|
||||||
|
@ -715,7 +715,7 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
public async getFormulaError(docSession: DocSession, tableId: string, colId: string,
|
public async getFormulaError(docSession: DocSession, tableId: string, colId: string,
|
||||||
rowId: number): Promise<CellValue> {
|
rowId: number): Promise<CellValue> {
|
||||||
if (!this._granularAccess.hasTableAccess(docSession, tableId)) { return null; }
|
if (!await this._granularAccess.hasTableAccess(docSession, tableId)) { return null; }
|
||||||
this.logInfo(docSession, "getFormulaError(%s, %s, %s, %s)",
|
this.logInfo(docSession, "getFormulaError(%s, %s, %s, %s)",
|
||||||
docSession, tableId, colId, rowId);
|
docSession, tableId, colId, rowId);
|
||||||
await this.waitForInitialization();
|
await this.waitForInitialization();
|
||||||
@ -1094,7 +1094,7 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// Migrate the document if needed.
|
// Migrate the document if needed.
|
||||||
const values = marshal.loads(docInfoData!);
|
const values = marshal.loads(docInfoData);
|
||||||
const versionCol = values.schemaVersion;
|
const versionCol = values.schemaVersion;
|
||||||
const docSchemaVersion = (versionCol && versionCol.length === 1 ? versionCol[0] : 0);
|
const docSchemaVersion = (versionCol && versionCol.length === 1 ? versionCol[0] : 0);
|
||||||
if (docSchemaVersion < schemaVersion) {
|
if (docSchemaVersion < schemaVersion) {
|
||||||
@ -1126,7 +1126,7 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
const tableNames: string[] = await this._rawPyCall('load_meta_tables', tablesData, columnsData);
|
const tableNames: string[] = await this._rawPyCall('load_meta_tables', tablesData, columnsData);
|
||||||
|
|
||||||
// Figure out which tables are on-demand.
|
// Figure out which tables are on-demand.
|
||||||
const tablesParsed: BulkColValues = marshal.loads(tablesData!);
|
const tablesParsed: BulkColValues = marshal.loads(tablesData);
|
||||||
const onDemandMap = zipObject(tablesParsed.tableId as string[], tablesParsed.onDemand);
|
const onDemandMap = zipObject(tablesParsed.tableId as string[], tablesParsed.onDemand);
|
||||||
const onDemandNames = remove(tableNames, (t) => onDemandMap[t]);
|
const onDemandNames = remove(tableNames, (t) => onDemandMap[t]);
|
||||||
|
|
||||||
@ -1183,7 +1183,7 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
|
|
||||||
const result: ApplyUAResult = await new Promise<ApplyUAResult>(
|
const result: ApplyUAResult = await new Promise<ApplyUAResult>(
|
||||||
(resolve, reject) =>
|
(resolve, reject) =>
|
||||||
this._sharing!.addUserAction({action, docSession, resolve, reject}));
|
this._sharing.addUserAction({action, docSession, resolve, reject}));
|
||||||
this.logDebug(docSession, "_applyUserActions returning %s", shortDesc(result));
|
this.logDebug(docSession, "_applyUserActions returning %s", shortDesc(result));
|
||||||
|
|
||||||
if (result.isModification) {
|
if (result.isModification) {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* of the client-side code.
|
* of the client-side code.
|
||||||
*/
|
*/
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import fetch, {RequestInit, Response as FetchResponse} from 'node-fetch';
|
import fetch, {Response as FetchResponse, RequestInit} from 'node-fetch';
|
||||||
|
|
||||||
import {ApiError} from 'app/common/ApiError';
|
import {ApiError} from 'app/common/ApiError';
|
||||||
import {getSlugIfNeeded, parseSubdomainStrictly} from 'app/common/gristUrls';
|
import {getSlugIfNeeded, parseSubdomainStrictly} from 'app/common/gristUrls';
|
||||||
|
@ -215,10 +215,9 @@ export class DocWorkerApi {
|
|||||||
|
|
||||||
// Initiate a fork. Used internally to implement ActiveDoc.fork. Only usable via a Permit.
|
// Initiate a fork. Used internally to implement ActiveDoc.fork. Only usable via a Permit.
|
||||||
this._app.post('/api/docs/:docId/create-fork', canEdit, throttled(async (req, res) => {
|
this._app.post('/api/docs/:docId/create-fork', canEdit, throttled(async (req, res) => {
|
||||||
const mreq = req as RequestWithLogin;
|
|
||||||
const docId = stringParam(req.params.docId);
|
const docId = stringParam(req.params.docId);
|
||||||
const srcDocId = stringParam(req.body.srcDocId);
|
const srcDocId = stringParam(req.body.srcDocId);
|
||||||
if (srcDocId !== mreq.specialPermit?.otherDocId) { throw new Error('access denied'); }
|
if (srcDocId !== req.specialPermit?.otherDocId) { throw new Error('access denied'); }
|
||||||
await this._docManager.storageManager.prepareFork(srcDocId, docId);
|
await this._docManager.storageManager.prepareFork(srcDocId, docId);
|
||||||
res.json({srcDocId, docId});
|
res.json({srcDocId, docId});
|
||||||
}));
|
}));
|
||||||
@ -249,7 +248,7 @@ export class DocWorkerApi {
|
|||||||
this._app.post('/api/docs/:docId/recover', canEdit, throttled(async (req, res) => {
|
this._app.post('/api/docs/:docId/recover', canEdit, throttled(async (req, res) => {
|
||||||
const recoveryModeRaw = req.body.recoveryMode;
|
const recoveryModeRaw = req.body.recoveryMode;
|
||||||
const recoveryMode = (typeof recoveryModeRaw === 'boolean') ? recoveryModeRaw : undefined;
|
const recoveryMode = (typeof recoveryModeRaw === 'boolean') ? recoveryModeRaw : undefined;
|
||||||
if (!this._isOwner(req)) { throw new Error('Only owners can control recovery mode'); }
|
if (!await this._isOwner(req)) { throw new Error('Only owners can control recovery mode'); }
|
||||||
const activeDoc = await this._docManager.fetchDoc(docSessionFromRequest(req), getDocId(req), recoveryMode);
|
const activeDoc = await this._docManager.fetchDoc(docSessionFromRequest(req), getDocId(req), recoveryMode);
|
||||||
res.json({
|
res.json({
|
||||||
recoveryMode: activeDoc.recoveryMode
|
recoveryMode: activeDoc.recoveryMode
|
||||||
|
@ -16,7 +16,8 @@ import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager';
|
|||||||
import {assertAccess, Authorizer, DocAuthorizer, DummyAuthorizer,
|
import {assertAccess, Authorizer, DocAuthorizer, DummyAuthorizer,
|
||||||
isSingleUserMode} from 'app/server/lib/Authorizer';
|
isSingleUserMode} from 'app/server/lib/Authorizer';
|
||||||
import {Client} from 'app/server/lib/Client';
|
import {Client} from 'app/server/lib/Client';
|
||||||
import {getDocSessionCachedDoc, makeExceptionalDocSession, makeOptDocSession, OptDocSession} from 'app/server/lib/DocSession';
|
import {getDocSessionCachedDoc, makeExceptionalDocSession, makeOptDocSession} from 'app/server/lib/DocSession';
|
||||||
|
import {OptDocSession} from 'app/server/lib/DocSession';
|
||||||
import * as docUtils from 'app/server/lib/docUtils';
|
import * as docUtils from 'app/server/lib/docUtils';
|
||||||
import {GristServer} from 'app/server/lib/GristServer';
|
import {GristServer} from 'app/server/lib/GristServer';
|
||||||
import {IDocStorageManager} from 'app/server/lib/IDocStorageManager';
|
import {IDocStorageManager} from 'app/server/lib/IDocStorageManager';
|
||||||
@ -498,7 +499,7 @@ export class DocManager extends EventEmitter {
|
|||||||
// TODO: We should be skeptical of the upload file to close a possible
|
// TODO: We should be skeptical of the upload file to close a possible
|
||||||
// security vulnerability. See https://phab.getgrist.com/T457.
|
// security vulnerability. See https://phab.getgrist.com/T457.
|
||||||
const docName = await this._createNewDoc(id);
|
const docName = await this._createNewDoc(id);
|
||||||
const docPath = await this.storageManager.getPath(docName);
|
const docPath: string = this.storageManager.getPath(docName);
|
||||||
await docUtils.copyFile(uploadInfo.files[0].absPath, docPath);
|
await docUtils.copyFile(uploadInfo.files[0].absPath, docPath);
|
||||||
await this.storageManager.addToStorage(docName);
|
await this.storageManager.addToStorage(docName);
|
||||||
return {title: basename, id: docName};
|
return {title: basename, id: docName};
|
||||||
|
@ -65,7 +65,12 @@ export class DocPluginManager {
|
|||||||
private _pluginInstances: PluginInstance[];
|
private _pluginInstances: PluginInstance[];
|
||||||
|
|
||||||
|
|
||||||
constructor(private _localPlugins: LocalPlugin[], private _appRoot: string, private _activeDoc: ActiveDoc, private _server: GristServer) {
|
constructor(
|
||||||
|
private _localPlugins: LocalPlugin[],
|
||||||
|
private _appRoot: string,
|
||||||
|
private _activeDoc: ActiveDoc,
|
||||||
|
private _server: GristServer
|
||||||
|
) {
|
||||||
this.gristDocAPI = new GristDocAPIImpl(_activeDoc);
|
this.gristDocAPI = new GristDocAPIImpl(_activeDoc);
|
||||||
this._pluginInstances = [];
|
this._pluginInstances = [];
|
||||||
this.ready = this._initialize();
|
this.ready = this._initialize();
|
||||||
|
@ -439,7 +439,9 @@ export class DocStorage implements ISQLiteDB {
|
|||||||
* Note that SQLite may contain tables that aren't used for Grist data (e.g. attachments), for
|
* Note that SQLite may contain tables that aren't used for Grist data (e.g. attachments), for
|
||||||
* which such encoding/marshalling is not used, and e.g. binary data is stored to BLOBs directly.
|
* which such encoding/marshalling is not used, and e.g. binary data is stored to BLOBs directly.
|
||||||
*/
|
*/
|
||||||
private static _encodeValue(marshaller: marshal.Marshaller, sqlType: string, val: any): Uint8Array|string|number|boolean {
|
private static _encodeValue(
|
||||||
|
marshaller: marshal.Marshaller, sqlType: string, val: any
|
||||||
|
): Uint8Array|string|number|boolean {
|
||||||
const marshalled = () => {
|
const marshalled = () => {
|
||||||
marshaller.marshal(val);
|
marshaller.marshal(val);
|
||||||
return marshaller.dump();
|
return marshaller.dump();
|
||||||
@ -1376,7 +1378,7 @@ export class DocStorage implements ISQLiteDB {
|
|||||||
// columns, or adding or removing or changing default values on a column."
|
// columns, or adding or removing or changing default values on a column."
|
||||||
const row = await this.get("PRAGMA schema_version");
|
const row = await this.get("PRAGMA schema_version");
|
||||||
assert(row && row.schema_version, "Could not retrieve schema_version.");
|
assert(row && row.schema_version, "Could not retrieve schema_version.");
|
||||||
const newSchemaVersion = row!.schema_version + 1;
|
const newSchemaVersion = row.schema_version + 1;
|
||||||
const tmpTableId = DocStorage._makeTmpTableId(tableId);
|
const tmpTableId = DocStorage._makeTmpTableId(tableId);
|
||||||
await this._getDB().runEach(
|
await this._getDB().runEach(
|
||||||
"PRAGMA writable_schema=ON",
|
"PRAGMA writable_schema=ON",
|
||||||
@ -1462,7 +1464,7 @@ export class DocStorage implements ISQLiteDB {
|
|||||||
* should be reasonably fast:
|
* should be reasonably fast:
|
||||||
* https://sqlite.org/tempfiles.html#temp_databases
|
* https://sqlite.org/tempfiles.html#temp_databases
|
||||||
*/
|
*/
|
||||||
public async _fetchQueryWithManyParameters(query: ExpandedQuery): Promise<Buffer> {
|
private async _fetchQueryWithManyParameters(query: ExpandedQuery): Promise<Buffer> {
|
||||||
const db = this._getDB();
|
const db = this._getDB();
|
||||||
return db.execTransaction(async () => {
|
return db.execTransaction(async () => {
|
||||||
const tableNames: string[] = [];
|
const tableNames: string[] = [];
|
||||||
@ -1490,7 +1492,7 @@ export class DocStorage implements ISQLiteDB {
|
|||||||
* Construct SQL for an ExpandedQuery. Expects that filters have been converted into
|
* Construct SQL for an ExpandedQuery. Expects that filters have been converted into
|
||||||
* a set of WHERE terms that should be ANDed.
|
* a set of WHERE terms that should be ANDed.
|
||||||
*/
|
*/
|
||||||
public _getSqlForQuery(query: ExpandedQuery, whereParts: string[]) {
|
private _getSqlForQuery(query: ExpandedQuery, whereParts: string[]) {
|
||||||
const whereClause = whereParts.length > 0 ? `WHERE ${whereParts.join(' AND ')}` : '';
|
const whereClause = whereParts.length > 0 ? `WHERE ${whereParts.join(' AND ')}` : '';
|
||||||
const limitClause = (typeof query.limit === 'number') ? `LIMIT ${query.limit}` : '';
|
const limitClause = (typeof query.limit === 'number') ? `LIMIT ${query.limit}` : '';
|
||||||
const joinClauses = query.joins ? query.joins.join(' ') : '';
|
const joinClauses = query.joins ? query.joins.join(' ') : '';
|
||||||
|
@ -196,7 +196,7 @@ export class DocStorageManager implements IDocStorageManager {
|
|||||||
* Electron version only. Shows the given doc in the file explorer.
|
* Electron version only. Shows the given doc in the file explorer.
|
||||||
*/
|
*/
|
||||||
public async showItemInFolder(docName: string): Promise<void> {
|
public async showItemInFolder(docName: string): Promise<void> {
|
||||||
this._shell.showItemInFolder(await this.getPath(docName));
|
this._shell.showItemInFolder(this.getPath(docName));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async closeStorage() {
|
public async closeStorage() {
|
||||||
|
@ -133,7 +133,7 @@ export class KeyMappedExternalStorage implements ExternalStorage {
|
|||||||
export class ChecksummedExternalStorage implements ExternalStorage {
|
export class ChecksummedExternalStorage implements ExternalStorage {
|
||||||
private _closed: boolean = false;
|
private _closed: boolean = false;
|
||||||
|
|
||||||
constructor(readonly label: string, private _ext: ExternalStorage, private _options: {
|
constructor(public readonly label: string, private _ext: ExternalStorage, private _options: {
|
||||||
maxRetries: number, // how many time to retry inconsistent downloads
|
maxRetries: number, // how many time to retry inconsistent downloads
|
||||||
initialDelayMs: number, // how long to wait before retrying
|
initialDelayMs: number, // how long to wait before retrying
|
||||||
localHash: PropStorage, // key/value store for hashes of downloaded content
|
localHash: PropStorage, // key/value store for hashes of downloaded content
|
||||||
|
@ -145,7 +145,7 @@ export class FlexServer implements GristServer {
|
|||||||
private _sendAppPage: (req: express.Request, resp: express.Response, options: ISendAppPageOptions) => Promise<void>;
|
private _sendAppPage: (req: express.Request, resp: express.Response, options: ISendAppPageOptions) => Promise<void>;
|
||||||
|
|
||||||
constructor(public port: number, public name: string = 'flexServer',
|
constructor(public port: number, public name: string = 'flexServer',
|
||||||
readonly options: FlexServerOptions = {}) {
|
public readonly options: FlexServerOptions = {}) {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
this.app.set('port', port);
|
this.app.set('port', port);
|
||||||
this.appRoot = getAppRoot();
|
this.appRoot = getAppRoot();
|
||||||
|
@ -724,7 +724,9 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
const ruler = await this._getRuler(cursor);
|
const ruler = await this._getRuler(cursor);
|
||||||
const tableId = getTableId(action);
|
const tableId = getTableId(action);
|
||||||
const ruleSets = ruler.ruleCollection.getAllColumnRuleSets(tableId);
|
const ruleSets = ruler.ruleCollection.getAllColumnRuleSets(tableId);
|
||||||
const colIds = new Set(([] as string[]).concat(...ruleSets.map(ruleSet => ruleSet.colIds === '*' ? [] : ruleSet.colIds)));
|
const colIds = new Set(([] as string[]).concat(
|
||||||
|
...ruleSets.map(ruleSet => ruleSet.colIds === '*' ? [] : ruleSet.colIds)
|
||||||
|
));
|
||||||
const access = await ruler.getAccess(cursor.docSession);
|
const access = await ruler.getAccess(cursor.docSession);
|
||||||
// Check columns in a consistent order, for determinism (easier testing).
|
// Check columns in a consistent order, for determinism (easier testing).
|
||||||
// TODO: could pool some work between columns by doing them together rather than one by one.
|
// TODO: could pool some work between columns by doing them together rather than one by one.
|
||||||
@ -1164,7 +1166,9 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
const rowsBefore = cloneDeep(tableData?.getTableDataAction() || ['TableData', '', [], {}] as TableDataAction);
|
const rowsBefore = cloneDeep(tableData?.getTableDataAction() || ['TableData', '', [], {}] as TableDataAction);
|
||||||
docData.receiveAction(docAction);
|
docData.receiveAction(docAction);
|
||||||
// If table is deleted, state afterwards doesn't matter.
|
// If table is deleted, state afterwards doesn't matter.
|
||||||
const rowsAfter = docData.getTable(tableId) ? cloneDeep(tableData?.getTableDataAction() || ['TableData', '', [], {}] as TableDataAction) : rowsBefore;
|
const rowsAfter = docData.getTable(tableId) ?
|
||||||
|
cloneDeep(tableData?.getTableDataAction() || ['TableData', '', [], {}] as TableDataAction) :
|
||||||
|
rowsBefore;
|
||||||
const step: ActionStep = {action: docAction, rowsBefore, rowsAfter};
|
const step: ActionStep = {action: docAction, rowsBefore, rowsAfter};
|
||||||
steps.push(step);
|
steps.push(step);
|
||||||
}
|
}
|
||||||
@ -1208,7 +1212,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
if (applied) {
|
if (applied) {
|
||||||
// Rules may have changed - back them off to a copy of their original state.
|
// Rules may have changed - back them off to a copy of their original state.
|
||||||
ruler = new Ruler(this);
|
ruler = new Ruler(this);
|
||||||
ruler.update(metaDocData);
|
await ruler.update(metaDocData);
|
||||||
}
|
}
|
||||||
let replaceRuler = false;
|
let replaceRuler = false;
|
||||||
for (const docAction of docActions) {
|
for (const docAction of docActions) {
|
||||||
@ -1228,7 +1232,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
replaceRuler = true;
|
replaceRuler = true;
|
||||||
} else if (replaceRuler) {
|
} else if (replaceRuler) {
|
||||||
ruler = new Ruler(this);
|
ruler = new Ruler(this);
|
||||||
ruler.update(metaDocData);
|
await ruler.update(metaDocData);
|
||||||
replaceRuler = false;
|
replaceRuler = false;
|
||||||
}
|
}
|
||||||
step.ruler = ruler;
|
step.ruler = ruler;
|
||||||
@ -1625,7 +1629,8 @@ export class CensorshipInfo {
|
|||||||
const tableId = tableRefToTableId.get(tableRef);
|
const tableId = tableRefToTableId.get(tableRef);
|
||||||
if (!tableId) { throw new Error('table not found'); }
|
if (!tableId) { throw new Error('table not found'); }
|
||||||
const colId = rec.get('colId') as string;
|
const colId = rec.get('colId') as string;
|
||||||
if (this.censoredTables.has(tableRef) || (colId !== 'manualSort' && permInfo.getColumnAccess(tableId, colId).perms.read === 'deny')) {
|
if (this.censoredTables.has(tableRef) ||
|
||||||
|
(colId !== 'manualSort' && permInfo.getColumnAccess(tableId, colId).perms.read === 'deny')) {
|
||||||
censoredColumnCodes.add(columnCode(tableRef, colId));
|
censoredColumnCodes.add(columnCode(tableRef, colId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,7 @@ export class HostedStorageManager implements IDocStorageManager {
|
|||||||
await this.prepareLocalDoc(docName, 'new');
|
await this.prepareLocalDoc(docName, 'new');
|
||||||
if (this._inventory) {
|
if (this._inventory) {
|
||||||
await this._inventory.create(docName);
|
await this._inventory.create(docName);
|
||||||
this._onInventoryChange(docName);
|
await this._onInventoryChange(docName);
|
||||||
}
|
}
|
||||||
this.markAsChanged(docName);
|
this.markAsChanged(docName);
|
||||||
}
|
}
|
||||||
|
@ -10,18 +10,14 @@ export const ITestingHooks = t.iface([], {
|
|||||||
"updateAuthToken": t.func("void", t.param("instId", "string"), t.param("authToken", "string")),
|
"updateAuthToken": t.func("void", t.param("instId", "string"), t.param("authToken", "string")),
|
||||||
"getAuthToken": t.func(t.union("string", "null"), t.param("instId", "string")),
|
"getAuthToken": t.func(t.union("string", "null"), t.param("instId", "string")),
|
||||||
"useTestToken": t.func("void", t.param("instId", "string"), t.param("token", "string")),
|
"useTestToken": t.func("void", t.param("instId", "string"), t.param("token", "string")),
|
||||||
"setLoginSessionProfile": t.func("void", t.param("gristSidCookie", "string"),
|
"setLoginSessionProfile": t.func("void", t.param("gristSidCookie", "string"), t.param("profile", t.union("UserProfile", "null")), t.param("org", "string", true)),
|
||||||
t.param("profile", t.union("UserProfile", "null")), t.param("org", "string", true)),
|
|
||||||
"setServerVersion": t.func("void", t.param("version", t.union("string", "null"))),
|
"setServerVersion": t.func("void", t.param("version", t.union("string", "null"))),
|
||||||
"disconnectClients": t.func("void"),
|
"disconnectClients": t.func("void"),
|
||||||
"commShutdown": t.func("void"),
|
"commShutdown": t.func("void"),
|
||||||
"commRestart": t.func("void"),
|
"commRestart": t.func("void"),
|
||||||
"commSetClientPersistence": t.func("void", t.param("ttlMs", "number")),
|
"commSetClientPersistence": t.func("void", t.param("ttlMs", "number")),
|
||||||
"closeDocs": t.func("void"),
|
"closeDocs": t.func("void"),
|
||||||
"setDocWorkerActivation": t.func("void", t.param("workerId", "string"),
|
"setDocWorkerActivation": t.func("void", t.param("workerId", "string"), t.param("active", t.union(t.lit('active'), t.lit('inactive'), t.lit('crash')))),
|
||||||
t.param("active", t.union(t.lit('active'),
|
|
||||||
t.lit('inactive'),
|
|
||||||
t.lit('crash')))),
|
|
||||||
"flushAuthorizerCache": t.func("void"),
|
"flushAuthorizerCache": t.func("void"),
|
||||||
"getDocClientCounts": t.func(t.array(t.tuple("string", "number"))),
|
"getDocClientCounts": t.func(t.array(t.tuple("string", "number"))),
|
||||||
"setActiveDocTimeout": t.func("number", t.param("seconds", "number")),
|
"setActiveDocTimeout": t.func("number", t.param("seconds", "number")),
|
||||||
@ -29,6 +25,5 @@ export const ITestingHooks = t.iface([], {
|
|||||||
|
|
||||||
const exportedTypeSuite: t.ITypeSuite = {
|
const exportedTypeSuite: t.ITypeSuite = {
|
||||||
ITestingHooks,
|
ITestingHooks,
|
||||||
UserProfile: t.name("object"),
|
|
||||||
};
|
};
|
||||||
export default exportedTypeSuite;
|
export default exportedTypeSuite;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {UserProfile} from 'app/common/LoginSessionAPI';
|
import {UserProfile} from 'app/common/LoginSessionAPI';
|
||||||
|
|
||||||
export interface ITestingHooks {
|
export interface ITestingHooks {
|
||||||
getOwnPort(): number;
|
getOwnPort(): Promise<number>;
|
||||||
getPort(): number;
|
getPort(): Promise<number>;
|
||||||
updateAuthToken(instId: string, authToken: string): Promise<void>;
|
updateAuthToken(instId: string, authToken: string): Promise<void>;
|
||||||
getAuthToken(instId: string): Promise<string|null>;
|
getAuthToken(instId: string): Promise<string|null>;
|
||||||
useTestToken(instId: string, token: string): Promise<void>;
|
useTestToken(instId: string, token: string): Promise<void>;
|
||||||
|
@ -62,7 +62,7 @@ export class OnDemandActions {
|
|||||||
// Check that the actionType can be applied without the sandbox and also that the action
|
// Check that the actionType can be applied without the sandbox and also that the action
|
||||||
// is on a data table.
|
// is on a data table.
|
||||||
const isOnDemandAction = ACTION_TYPES.has(a[0] as string);
|
const isOnDemandAction = ACTION_TYPES.has(a[0] as string);
|
||||||
const isDataTableAction = typeof a[1] === 'string' && !(a[1] as string).startsWith('_grist_');
|
const isDataTableAction = typeof a[1] === 'string' && !a[1].startsWith('_grist_');
|
||||||
if (a[0] === 'ApplyUndoActions') {
|
if (a[0] === 'ApplyUndoActions') {
|
||||||
// Split actions inside the undo action array.
|
// Split actions inside the undo action array.
|
||||||
const [undoNormal, undoOnDemand] = this.splitByOnDemand(a[1] as UserAction[]);
|
const [undoNormal, undoOnDemand] = this.splitByOnDemand(a[1] as UserAction[]);
|
||||||
|
@ -155,7 +155,7 @@ export class Sharing {
|
|||||||
private async _rebaseLocalActions(): Promise<void> {
|
private async _rebaseLocalActions(): Promise<void> {
|
||||||
const rebaseQueue: Deque<UserActionBundle> = new Deque<UserActionBundle>();
|
const rebaseQueue: Deque<UserActionBundle> = new Deque<UserActionBundle>();
|
||||||
try {
|
try {
|
||||||
await this.createCheckpoint();
|
this.createCheckpoint();
|
||||||
const actions: LocalActionBundle[] = await this._actionHistory.fetchAllLocal();
|
const actions: LocalActionBundle[] = await this._actionHistory.fetchAllLocal();
|
||||||
assert(actions.length > 0);
|
assert(actions.length > 0);
|
||||||
await this.doApplyUserActionBundle(this._createUndo(actions), null);
|
await this.doApplyUserActionBundle(this._createUndo(actions), null);
|
||||||
@ -163,7 +163,7 @@ export class Sharing {
|
|||||||
await this._actionHistory.clearLocalActions();
|
await this._actionHistory.clearLocalActions();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error("Can't undo local actions; sharing is off");
|
log.error("Can't undo local actions; sharing is off");
|
||||||
await this.rollbackToCheckpoint();
|
this.rollbackToCheckpoint();
|
||||||
// TODO this.disconnect();
|
// TODO this.disconnect();
|
||||||
// TODO errorState = true;
|
// TODO errorState = true;
|
||||||
return;
|
return;
|
||||||
@ -185,11 +185,11 @@ export class Sharing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rebaseFailures.length > 0) {
|
if (rebaseFailures.length > 0) {
|
||||||
await this.createBackupAtCheckpoint();
|
this.createBackupAtCheckpoint();
|
||||||
// TODO we should notify the user too.
|
// TODO we should notify the user too.
|
||||||
log.error('Rebase failed to reapply some of your actions, backup of local at...');
|
log.error('Rebase failed to reapply some of your actions, backup of local at...');
|
||||||
}
|
}
|
||||||
await this.releaseCheckpoint();
|
this.releaseCheckpoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================================================
|
// ======================================================================
|
||||||
@ -374,7 +374,9 @@ export class Sharing {
|
|||||||
const docActions = getEnvContent(sandboxActionBundle.stored).concat(
|
const docActions = getEnvContent(sandboxActionBundle.stored).concat(
|
||||||
getEnvContent(sandboxActionBundle.calc));
|
getEnvContent(sandboxActionBundle.calc));
|
||||||
|
|
||||||
const accessControl = this._activeDoc.getGranularAccessForBundle(docSession || makeExceptionalDocSession('share'), docActions, undo, userActions);
|
const accessControl = this._activeDoc.getGranularAccessForBundle(
|
||||||
|
docSession || makeExceptionalDocSession('share'), docActions, undo, userActions
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
// TODO: see if any of the code paths that have no docSession are relevant outside
|
// TODO: see if any of the code paths that have no docSession are relevant outside
|
||||||
// of tests.
|
// of tests.
|
||||||
|
@ -6,13 +6,15 @@ import * as Comm from 'app/server/lib/Comm';
|
|||||||
import {ILoginSession} from 'app/server/lib/ILoginSession';
|
import {ILoginSession} from 'app/server/lib/ILoginSession';
|
||||||
import * as log from 'app/server/lib/log';
|
import * as log from 'app/server/lib/log';
|
||||||
import {IMessage, Rpc} from 'grain-rpc';
|
import {IMessage, Rpc} from 'grain-rpc';
|
||||||
import {createCheckers} from 'ts-interface-checker';
|
import * as t from 'ts-interface-checker';
|
||||||
import {FlexServer} from './FlexServer';
|
import {FlexServer} from './FlexServer';
|
||||||
import {IInstanceManager} from './IInstanceManager';
|
import {IInstanceManager} from './IInstanceManager';
|
||||||
import {ITestingHooks} from './ITestingHooks';
|
import {ITestingHooks} from './ITestingHooks';
|
||||||
import ITestingHooksTI from './ITestingHooks-ti';
|
import ITestingHooksTI from './ITestingHooks-ti';
|
||||||
import {connect, fromCallback} from './serverUtils';
|
import {connect, fromCallback} from './serverUtils';
|
||||||
|
|
||||||
|
const tiCheckers = t.createCheckers(ITestingHooksTI, {UserProfile: t.name("object")});
|
||||||
|
|
||||||
export function startTestingHooks(socketPath: string, port: number, instanceManager: IInstanceManager,
|
export function startTestingHooks(socketPath: string, port: number, instanceManager: IInstanceManager,
|
||||||
comm: Comm, flexServer: FlexServer,
|
comm: Comm, flexServer: FlexServer,
|
||||||
workerServers: FlexServer[]): Promise<net.Server> {
|
workerServers: FlexServer[]): Promise<net.Server> {
|
||||||
@ -27,7 +29,7 @@ export function startTestingHooks(socketPath: string, port: number, instanceMana
|
|||||||
// Register the testing implementation.
|
// Register the testing implementation.
|
||||||
rpc.registerImpl('testing',
|
rpc.registerImpl('testing',
|
||||||
new TestingHooks(port, instanceManager, comm, flexServer, workerServers),
|
new TestingHooks(port, instanceManager, comm, flexServer, workerServers),
|
||||||
createCheckers(ITestingHooksTI).ITestingHooks);
|
tiCheckers.ITestingHooks);
|
||||||
});
|
});
|
||||||
server.listen(socketPath);
|
server.listen(socketPath);
|
||||||
});
|
});
|
||||||
@ -47,7 +49,7 @@ export interface TestingHooksClient extends ITestingHooks {
|
|||||||
export async function connectTestingHooks(socketPath: string): Promise<TestingHooksClient> {
|
export async function connectTestingHooks(socketPath: string): Promise<TestingHooksClient> {
|
||||||
const socket = await connect(socketPath);
|
const socket = await connect(socketPath);
|
||||||
const rpc = connectToSocket(new Rpc({logger: {}}), socket);
|
const rpc = connectToSocket(new Rpc({logger: {}}), socket);
|
||||||
return Object.assign(rpc.getStub<TestingHooks>('testing', createCheckers(ITestingHooksTI).ITestingHooks), {
|
return Object.assign(rpc.getStub<TestingHooks>('testing', tiCheckers.ITestingHooks), {
|
||||||
close: () => socket.end(),
|
close: () => socket.end(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -61,12 +63,12 @@ export class TestingHooks implements ITestingHooks {
|
|||||||
private _workerServers: FlexServer[]
|
private _workerServers: FlexServer[]
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public getOwnPort(): number {
|
public async getOwnPort(): Promise<number> {
|
||||||
log.info("TestingHooks.getOwnPort called");
|
log.info("TestingHooks.getOwnPort called");
|
||||||
return this._server.getOwnPort();
|
return this._server.getOwnPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPort(): number {
|
public async getPort(): Promise<number> {
|
||||||
log.info("TestingHooks.getPort called");
|
log.info("TestingHooks.getPort called");
|
||||||
return this._port;
|
return this._port;
|
||||||
}
|
}
|
||||||
@ -100,7 +102,7 @@ export class TestingHooks implements ITestingHooks {
|
|||||||
log.info("TestingHooks.setServerVersion called with", version);
|
log.info("TestingHooks.setServerVersion called with", version);
|
||||||
this._comm.setServerVersion(version);
|
this._comm.setServerVersion(version);
|
||||||
for (const server of this._workerServers) {
|
for (const server of this._workerServers) {
|
||||||
await server.comm.setServerVersion(version);
|
server.comm.setServerVersion(version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +110,7 @@ export class TestingHooks implements ITestingHooks {
|
|||||||
log.info("TestingHooks.disconnectClients called");
|
log.info("TestingHooks.disconnectClients called");
|
||||||
this._comm.destroyAllClients();
|
this._comm.destroyAllClients();
|
||||||
for (const server of this._workerServers) {
|
for (const server of this._workerServers) {
|
||||||
await server.comm.destroyAllClients();
|
server.comm.destroyAllClients();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +136,7 @@ export class TestingHooks implements ITestingHooks {
|
|||||||
log.info("TestingHooks.setClientPersistence called with", ttlMs);
|
log.info("TestingHooks.setClientPersistence called with", ttlMs);
|
||||||
this._comm.testSetClientPersistence(ttlMs);
|
this._comm.testSetClientPersistence(ttlMs);
|
||||||
for (const server of this._workerServers) {
|
for (const server of this._workerServers) {
|
||||||
await server.comm.testSetClientPersistence(ttlMs);
|
server.comm.testSetClientPersistence(ttlMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +176,7 @@ export class TestingHooks implements ITestingHooks {
|
|||||||
log.info("TestingHooks.flushAuthorizerCache called");
|
log.info("TestingHooks.flushAuthorizerCache called");
|
||||||
this._server.dbManager.flushDocAuthCache();
|
this._server.dbManager.flushDocAuthCache();
|
||||||
for (const server of this._workerServers) {
|
for (const server of this._workerServers) {
|
||||||
await server.dbManager.flushDocAuthCache();
|
server.dbManager.flushDocAuthCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ export async function createTmpDir(options: tmp.Options): Promise<TmpDirResult>
|
|||||||
try {
|
try {
|
||||||
// Still call the original callback, so that `tmp` module doesn't keep remembering about
|
// Still call the original callback, so that `tmp` module doesn't keep remembering about
|
||||||
// this directory and doesn't try to delete it again on exit.
|
// this directory and doesn't try to delete it again on exit.
|
||||||
tmpCleanup();
|
await tmpCleanup();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// OK if it fails because the dir is already removed.
|
// OK if it fails because the dir is already removed.
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"plugins": [{
|
"plugins": [{
|
||||||
"name": "typescript-tslint-plugin"
|
"name": "typescript-eslint-language-service"
|
||||||
}],
|
}],
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
@ -4,7 +4,7 @@ export type ProductFlavor = string;
|
|||||||
export interface CustomTheme {
|
export interface CustomTheme {
|
||||||
bodyClassName?: string;
|
bodyClassName?: string;
|
||||||
wideLogo?: boolean;
|
wideLogo?: boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
export function getTheme(flavor: string): CustomTheme {
|
export function getTheme(flavor: string): CustomTheme {
|
||||||
return {
|
return {
|
||||||
|
@ -15,7 +15,7 @@ import { decodeUrl } from 'app/common/gristUrls';
|
|||||||
import { FullUser, UserProfile } from 'app/common/LoginSessionAPI';
|
import { FullUser, UserProfile } from 'app/common/LoginSessionAPI';
|
||||||
import { resetOrg } from 'app/common/resetOrg';
|
import { resetOrg } from 'app/common/resetOrg';
|
||||||
import { TestState } from 'app/common/TestState';
|
import { TestState } from 'app/common/TestState';
|
||||||
import { DocStateComparison, Organization as APIOrganization, UserAPIImpl, Workspace } from 'app/common/UserAPI';
|
import { Organization as APIOrganization, DocStateComparison, UserAPIImpl, Workspace } from 'app/common/UserAPI';
|
||||||
import { Organization } from 'app/gen-server/entity/Organization';
|
import { Organization } from 'app/gen-server/entity/Organization';
|
||||||
import { Product } from 'app/gen-server/entity/Product';
|
import { Product } from 'app/gen-server/entity/Product';
|
||||||
import { create } from 'app/server/lib/create';
|
import { create } from 'app/server/lib/create';
|
||||||
@ -163,7 +163,7 @@ export async function selectAll() {
|
|||||||
* Returns a WebElementPromise for the .viewsection_content element for the section which contains
|
* Returns a WebElementPromise for the .viewsection_content element for the section which contains
|
||||||
* the given RegExp content.
|
* the given RegExp content.
|
||||||
*/
|
*/
|
||||||
export function getSection(sectionOrTitle: string|WebElement): WebElement {
|
export function getSection(sectionOrTitle: string|WebElement): WebElement|WebElementPromise {
|
||||||
if (typeof sectionOrTitle !== 'string') { return sectionOrTitle; }
|
if (typeof sectionOrTitle !== 'string') { return sectionOrTitle; }
|
||||||
return driver.find(`.test-viewsection-title[value="${sectionOrTitle}" i]`)
|
return driver.find(`.test-viewsection-title[value="${sectionOrTitle}" i]`)
|
||||||
.findClosest('.viewsection_content');
|
.findClosest('.viewsection_content');
|
||||||
@ -1187,7 +1187,7 @@ export class Session {
|
|||||||
|
|
||||||
// Wipe the current site. The current user ends up being its only owner and manager.
|
// Wipe the current site. The current user ends up being its only owner and manager.
|
||||||
public async resetSite() {
|
public async resetSite() {
|
||||||
return resetOrg(await this.createHomeApi(), this.settings.org);
|
return resetOrg(this.createHomeApi(), this.settings.org);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a session configured for the current session's site but a different user.
|
// Return a session configured for the current session's site but a different user.
|
||||||
|
@ -55,7 +55,7 @@ export class TestServerMerged implements IMochaServer {
|
|||||||
public async restart(reset: boolean = false) {
|
public async restart(reset: boolean = false) {
|
||||||
if (this.isExternalServer()) { return; }
|
if (this.isExternalServer()) { return; }
|
||||||
if (this._starts > 0) {
|
if (this._starts > 0) {
|
||||||
await this.resume();
|
this.resume();
|
||||||
await this.stop();
|
await this.stop();
|
||||||
}
|
}
|
||||||
this._starts++;
|
this._starts++;
|
||||||
|
Loading…
Reference in New Issue
Block a user