(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:
George Gevoian
2022-05-16 10:41:12 -07:00
parent cbdbe3f605
commit f48d579f64
23 changed files with 559 additions and 185 deletions

View File

@@ -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);