mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add API endpoint to get site usage summary
Summary: The summary includes a count of documents that are approaching limits, in grace period, or delete-only. The endpoint is only accessible to site owners, and is currently unused. A follow-up diff will add usage banners to the site home page, which will use the response from the endpoint to communicate usage information to owners. Test Plan: Browser and server tests. Reviewers: alexmojaki Reviewed By: alexmojaki Differential Revision: https://phab.getgrist.com/D3420
This commit is contained in:
@@ -5,7 +5,7 @@ import {ActionGroup} from 'app/common/ActionGroup';
|
||||
import {ActiveDocAPI, ApplyUAOptions, ApplyUAResult} from 'app/common/ActiveDocAPI';
|
||||
import {DocAction, UserAction} from 'app/common/DocActions';
|
||||
import {OpenLocalDocResult} from 'app/common/DocListAPI';
|
||||
import {DocUsage} from 'app/common/DocUsage';
|
||||
import {FilteredDocUsageSummary} from 'app/common/DocUsage';
|
||||
import {docUrl} from 'app/common/urlUtils';
|
||||
import {Events as BackboneEvents} from 'backbone';
|
||||
import {Disposable, Emitter} from 'grainjs';
|
||||
@@ -18,7 +18,7 @@ export interface DocUserAction extends CommMessage {
|
||||
data: {
|
||||
docActions: DocAction[];
|
||||
actionGroup: ActionGroup;
|
||||
docUsage: DocUsage;
|
||||
docUsage: FilteredDocUsageSummary;
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ export class DocUsageBanner extends Disposable {
|
||||
// Whether the banner is vertically expanded on narrow screens.
|
||||
private readonly _isExpanded = Observable.create(this, true);
|
||||
|
||||
private readonly _currentDoc = this._docPageModel.currentDoc;
|
||||
private readonly _currentDocId = this._docPageModel.currentDocId;
|
||||
private readonly _dataLimitStatus = this._docPageModel.dataLimitStatus;
|
||||
private readonly _currentDocUsage = this._docPageModel.currentDocUsage;
|
||||
private readonly _currentOrg = this._docPageModel.currentOrg;
|
||||
|
||||
private readonly _currentOrg = Computed.create(this, this._currentDoc, (_use, doc) => {
|
||||
return doc?.workspace.org ?? null;
|
||||
private readonly _dataLimitStatus = Computed.create(this, this._currentDocUsage, (_use, usage) => {
|
||||
return usage?.dataLimitStatus ?? null;
|
||||
});
|
||||
|
||||
private readonly _shouldShowBanner: Computed<boolean> =
|
||||
|
||||
@@ -30,13 +30,23 @@ const ACCESS_DENIED_MESSAGE = 'Usage statistics are only available to users with
|
||||
*/
|
||||
export class DocumentUsage extends Disposable {
|
||||
private readonly _currentDoc = this._docPageModel.currentDoc;
|
||||
private readonly _dataLimitStatus = this._docPageModel.dataLimitStatus;
|
||||
private readonly _rowCount = this._docPageModel.rowCount;
|
||||
private readonly _dataSizeBytes = this._docPageModel.dataSizeBytes;
|
||||
private readonly _attachmentsSizeBytes = this._docPageModel.attachmentsSizeBytes;
|
||||
private readonly _currentDocUsage = this._docPageModel.currentDocUsage;
|
||||
private readonly _currentOrg = this._docPageModel.currentOrg;
|
||||
|
||||
private readonly _currentOrg = Computed.create(this, this._currentDoc, (_use, doc) => {
|
||||
return doc?.workspace.org ?? null;
|
||||
private readonly _dataLimitStatus = Computed.create(this, this._currentDocUsage, (_use, usage) => {
|
||||
return usage?.dataLimitStatus ?? null;
|
||||
});
|
||||
|
||||
private readonly _rowCount = Computed.create(this, this._currentDocUsage, (_use, usage) => {
|
||||
return usage?.rowCount;
|
||||
});
|
||||
|
||||
private readonly _dataSizeBytes = Computed.create(this, this._currentDocUsage, (_use, usage) => {
|
||||
return usage?.dataSizeBytes;
|
||||
});
|
||||
|
||||
private readonly _attachmentsSizeBytes = Computed.create(this, this._currentDocUsage, (_use, usage) => {
|
||||
return usage?.attachmentsSizeBytes;
|
||||
});
|
||||
|
||||
private readonly _rowMetrics: Computed<MetricOptions | null> =
|
||||
@@ -102,7 +112,9 @@ export class DocumentUsage extends Disposable {
|
||||
Computed.create(
|
||||
this, this._currentDoc, this._rowCount, this._dataSizeBytes, this._attachmentsSizeBytes,
|
||||
(_use, doc, rowCount, dataSize, attachmentsSize) => {
|
||||
return !doc || [rowCount, dataSize, attachmentsSize].some(metric => metric === 'pending');
|
||||
return !doc || [rowCount, dataSize, attachmentsSize].some(metric => {
|
||||
return metric === 'pending' || metric === undefined;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -478,7 +478,7 @@ export class GristDoc extends DisposableWithEvents {
|
||||
if (schemaUpdated) {
|
||||
this.trigger('schemaUpdateAction', docActions);
|
||||
}
|
||||
this.docPageModel.updateDocUsage(message.data.docUsage);
|
||||
this.docPageModel.updateCurrentDocUsage(message.data.docUsage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {confirmModal} from 'app/client/ui2018/modals';
|
||||
import {AsyncFlow, CancelledError, FlowRunner} from 'app/common/AsyncFlow';
|
||||
import {delay} from 'app/common/delay';
|
||||
import {OpenDocMode, UserOverride} from 'app/common/DocListAPI';
|
||||
import {AttachmentsSize, DataLimitStatus, DataSize, DocUsage, RowCount} from 'app/common/DocUsage';
|
||||
import {FilteredDocUsageSummary} from 'app/common/DocUsage';
|
||||
import {IGristUrlState, parseUrlId, UrlIdParts} from 'app/common/gristUrls';
|
||||
import {getReconnectTimeout} from 'app/common/gutil';
|
||||
import {canEdit} from 'app/common/roles';
|
||||
@@ -44,6 +44,7 @@ export interface DocPageModel {
|
||||
|
||||
appModel: AppModel;
|
||||
currentDoc: Observable<DocInfo|null>;
|
||||
currentDocUsage: Observable<FilteredDocUsageSummary|null>;
|
||||
|
||||
// This block is to satisfy previous interface, but usable as this.currentDoc.get().id, etc.
|
||||
currentDocId: Observable<string|undefined>;
|
||||
@@ -66,16 +67,11 @@ export interface DocPageModel {
|
||||
|
||||
gristDoc: Observable<GristDoc|null>; // Instance of GristDoc once it exists.
|
||||
|
||||
dataLimitStatus: Observable<DataLimitStatus>;
|
||||
rowCount: Observable<RowCount>;
|
||||
dataSizeBytes: Observable<DataSize>;
|
||||
attachmentsSizeBytes: Observable<AttachmentsSize>;
|
||||
|
||||
createLeftPane(leftPanelOpen: Observable<boolean>): DomArg;
|
||||
renameDoc(value: string): Promise<void>;
|
||||
updateCurrentDoc(urlId: string, openMode: OpenDocMode): Promise<Document>;
|
||||
refreshCurrentDoc(doc: DocInfo): Promise<Document>;
|
||||
updateDocUsage(docUsage: DocUsage): void;
|
||||
updateCurrentDocUsage(docUsage: FilteredDocUsageSummary): void;
|
||||
}
|
||||
|
||||
export interface ImportSource {
|
||||
@@ -88,6 +84,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
||||
public readonly pageType = "doc";
|
||||
|
||||
public readonly currentDoc = Observable.create<DocInfo|null>(this, null);
|
||||
public readonly currentDocUsage = Observable.create<FilteredDocUsageSummary|null>(this, null);
|
||||
|
||||
public readonly currentUrlId = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.urlId : undefined);
|
||||
public readonly currentDocId = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.id : undefined);
|
||||
@@ -112,11 +109,6 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
||||
// Observable set to the instance of GristDoc once it's created.
|
||||
public readonly gristDoc = Observable.create<GristDoc|null>(this, null);
|
||||
|
||||
public readonly dataLimitStatus = Observable.create<DataLimitStatus>(this, null);
|
||||
public readonly rowCount = Observable.create<RowCount>(this, 'pending');
|
||||
public readonly dataSizeBytes = Observable.create<DataSize>(this, 'pending');
|
||||
public readonly attachmentsSizeBytes = Observable.create<AttachmentsSize>(this, 'pending');
|
||||
|
||||
// Combination of arguments needed to open a doc (docOrUrlId + openMod). It's obtained from the
|
||||
// URL, and when it changes, we need to re-open.
|
||||
// If making a comparison, the id of the document we are comparing with is also included
|
||||
@@ -199,6 +191,10 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
||||
return this.updateCurrentDoc(doc.urlId || doc.id, doc.openMode);
|
||||
}
|
||||
|
||||
public updateCurrentDocUsage(docUsage: FilteredDocUsageSummary) {
|
||||
this.currentDocUsage.set(docUsage);
|
||||
}
|
||||
|
||||
// Replace the URL without reloading the doc.
|
||||
public updateUrlNoReload(urlId: string, urlOpenMode: OpenDocMode, options: {replace: boolean}) {
|
||||
const state = urlState().state.get();
|
||||
@@ -208,13 +204,6 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
||||
return urlState().pushUrl(nextState, {avoidReload: true, ...options});
|
||||
}
|
||||
|
||||
public updateDocUsage(docUsage: DocUsage) {
|
||||
this.rowCount.set(docUsage.rowCount);
|
||||
this.dataLimitStatus.set(docUsage.dataLimitStatus);
|
||||
this.dataSizeBytes.set(docUsage.dataSizeBytes);
|
||||
this.attachmentsSizeBytes.set(docUsage.attachmentsSizeBytes);
|
||||
}
|
||||
|
||||
private _onOpenError(err: Error) {
|
||||
if (err instanceof CancelledError) {
|
||||
// This means that we started loading a new doc before the previous one finished loading.
|
||||
@@ -273,7 +262,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
||||
this.currentDoc.set({...doc});
|
||||
}
|
||||
if (openDocResponse.docUsage) {
|
||||
this.updateDocUsage(openDocResponse.docUsage);
|
||||
this.updateCurrentDocUsage(openDocResponse.docUsage);
|
||||
}
|
||||
const gdModule = await gristDocModulePromise;
|
||||
const docComm = gdModule.DocComm.create(flow, comm, openDocResponse, doc.id, this.appModel.notifier);
|
||||
|
||||
Reference in New Issue
Block a user