(core) Improve API Console and link from Document Settings.

Summary:
Changes to building and serving:
- Remove unpkg dependencies, add npm module for swagger-ui-dist instead.
- Move apiconsole JS logic into core/app/client/apiconsole.ts, and use TypeScript.
- Add symlinks to swagger in static/ and core/static/.
- Refactor loadScript, and add loadCssFile; use these to load swagger-ui resources.

Changes to console itself:
- Support docId, workspaceId, orgId URL parameters. When present, the matching
  value in dropdowns is moved to the front and marked as "(Current)".
- Fix the ordering of example values, particularly for workspaces.
- Remove unwanted example values.
- Hide confusing "Authorize" button.
- Hide API keys, and rely consistently on cookies for executing API calls.

Integration into Grist:
- Added a button to Document Settings, just under document ID in "API".
- The button opens a separate page, passing in org, workspace, and doc info for the current doc.

Test Plan: Only tested manually, no automated tests yet.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D4173
This commit is contained in:
Dmitry S
2024-01-26 23:21:34 -05:00
parent be0b4a1968
commit 11afc08f65
17 changed files with 409 additions and 266 deletions

View File

@@ -45,7 +45,6 @@ import {
getTableId,
isSchemaAction,
TableDataAction,
TableRecordValue,
toTableDataAction,
UserAction
} from 'app/common/DocActions';
@@ -81,6 +80,7 @@ import {guessColInfo} from 'app/common/ValueGuesser';
import {parseUserAction} from 'app/common/ValueParser';
import {Document} from 'app/gen-server/entity/Document';
import {Share} from 'app/gen-server/entity/Share';
import {RecordWithStringId} from 'app/plugin/DocApiTypes';
import {ParseFileResult, ParseOptions} from 'app/plugin/FileParserAPI';
import {AccessTokenOptions, AccessTokenResult, GristDocAPI, UIRowId} from 'app/plugin/GristAPI';
import {compileAclFormula} from 'app/server/lib/ACLFormula';
@@ -1115,7 +1115,7 @@ export class ActiveDoc extends EventEmitter {
public async getTableCols(
docSession: OptDocSession,
tableId: string,
includeHidden = false): Promise<TableRecordValue[]> {
includeHidden = false): Promise<RecordWithStringId[]> {
const metaTables = await this.fetchMetaTables(docSession);
const tableRef = tableIdToRef(metaTables, tableId);
const [, , colRefs, columnData] = metaTables._grist_Tables_column;
@@ -1123,7 +1123,7 @@ export class ActiveDoc extends EventEmitter {
// colId is pulled out of fields and used as the root id
const fieldNames = without(Object.keys(columnData), "colId");
const columns: TableRecordValue[] = [];
const columns: RecordWithStringId[] = [];
(columnData.colId as string[]).forEach((id, index) => {
const hasNoId = !id;
const isHidden = hasNoId || id === "manualSort" || id.startsWith("gristHelper_");
@@ -1132,7 +1132,7 @@ export class ActiveDoc extends EventEmitter {
if (skip) {
return;
}
const column: TableRecordValue = { id, fields: { colRef: colRefs[index] } };
const column: RecordWithStringId = { id, fields: { colRef: colRefs[index] } };
for (const key of fieldNames) {
column.fields[key] = columnData[key][index];
}

View File

@@ -458,8 +458,8 @@ export class DocWorkerApi {
this._app.get('/api/docs/:docId/tables', canView,
withDoc(async (activeDoc, req, res) => {
const records = await getTableRecords(activeDoc, req, { optTableId: "_grist_Tables" });
const tables = records.map((record) => ({
id: record.fields.tableId,
const tables: Types.RecordWithStringId[] = records.map((record) => ({
id: String(record.fields.tableId),
fields: {
..._.omit(record.fields, "tableId"),
tableRef: record.id,

View File

@@ -198,8 +198,12 @@ export class TableMetadataLoader {
for (const tableId of [...newPushes].sort()) {
// Put a limit on the number of outstanding pushes permitted.
if (this._pushes.size >= this._pushed.size + 3) { break; }
this._pushes.set(tableId, this._counted(this.opPush(tableId)));
}
const promise = this._counted(this.opPush(tableId));
this._pushes.set(tableId, promise);
// Mark the promise as handled to avoid "unhandledRejection", but without affecting other
// code (which will still see `promise`, not the new promise returned by `.catch()`).
promise.catch(() => {});
}
}
// Wrapper to keep track of pending promises.