From c6d66e15bfb98b2ffe087aca9bc5eafddc6f1fe3 Mon Sep 17 00:00:00 2001 From: Paul Fitzpatrick Date: Wed, 23 Mar 2022 09:41:34 -0400 Subject: [PATCH] (core) configure typedoc for generating plugin api documentation Summary: This annotates the plugin api sufficiently to generate some documentation for it. See https://github.com/gristlabs/grist-help/pull/139 Contains some small code tweaks for things that caused typedoc some trouble. Test Plan: manual inspection of output Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3342 --- app/plugin/GristAPI.ts | 52 ++++++++---- app/plugin/README.md | 1 + app/plugin/RenderOptions.ts | 2 + app/plugin/TableOperations.ts | 30 ++++--- app/plugin/grist-plugin-api.ts | 143 +++++++++++++++++++++++++-------- test/nbrowser/testServer.ts | 2 +- test/server/gristClient.ts | 2 +- tsconfig.json | 11 ++- 8 files changed, 178 insertions(+), 65 deletions(-) create mode 100644 app/plugin/README.md diff --git a/app/plugin/GristAPI.ts b/app/plugin/GristAPI.ts index ed2b5773..dc604ac0 100644 --- a/app/plugin/GristAPI.ts +++ b/app/plugin/GristAPI.ts @@ -69,40 +69,58 @@ export interface GristAPI { } /** - * GristDocAPI interface is implemented by Grist, and allows getting information from and - * interacting with the Grist document to which a plugin is attached. + * Allows getting information from and nteracting with the Grist document to which a plugin or widget is attached. */ export interface GristDocAPI { - // Returns the docName that identifies the document. + /** + * Returns an identifier for the document. + */ getDocName(): Promise; - // Returns a sorted list of table IDs. + /** + * Returns a sorted list of table IDs. + */ listTables(): Promise; - // Returns a complete table of data in the format {colId: [values]}, including the 'id' column. - // Do not modify the returned arrays in-place, especially if used directly (not over RPC). - // TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because - // ts-interface-builder does not properly support index-signature. + /** + * Returns a complete table of data in the format {colId: [values]}, including the 'id' column. + * Do not modify the returned arrays in-place, especially if used directly (not over RPC). + * TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because + * ts-interface-builder does not properly support index-signature. + */ fetchTable(tableId: string): Promise; - // Applies an array of user actions. - // todo: return type should be Promise, but this requires importing modules from - // `app/common` which is not currently supported by the build. + /** + * Applies an array of user actions. + * TODO: return type should be Promise, but this requires importing modules from + * `app/common` which is not currently supported by the build. + */ applyUserActions(actions: any[][], options?: any): Promise; } +/** + * Interface for the data backing a single widget. + */ export interface GristView { - // Like fetchTable, but gets data for the custom section specifically, if there is any. - // TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because - // ts-interface-builder does not properly support index-signature. + /** + * Like [[GristDocAPI.fetchTable]], but gets data for the custom section specifically, if there is any. + * TODO: return type is Promise{[colId: string]: CellValue[]}> but cannot be specified because + * ts-interface-builder does not properly support index-signature. + */ fetchSelectedTable(): Promise; - // Similar TODO to fetchSelectedTable for return type. + /** + * Similar TODO to `fetchSelectedTable()` for return type. + */ fetchSelectedRecord(rowId: number): Promise; - // Allow custom widget to be listed as a possible source for linking with SELECT BY. + /** + * Allow custom widget to be listed as a possible source for linking with SELECT BY. + */ allowSelectBy(): Promise; - // Set the list of selected rows to be used against any linked widget. Requires `allowSelectBy()`. + /** + * Set the list of selected rows to be used against any linked widget. Requires `allowSelectBy()`. + */ setSelectedRows(rowIds: number[]): Promise; } diff --git a/app/plugin/README.md b/app/plugin/README.md new file mode 100644 index 00000000..aba18cc9 --- /dev/null +++ b/app/plugin/README.md @@ -0,0 +1 @@ +Methods here are available for use in Grist custom widgets. diff --git a/app/plugin/RenderOptions.ts b/app/plugin/RenderOptions.ts index 82a94156..3ae95100 100644 --- a/app/plugin/RenderOptions.ts +++ b/app/plugin/RenderOptions.ts @@ -1,5 +1,7 @@ /** * Where to append the content that a plugin renders. + * + * @internal */ export type RenderTarget = "fullscreen" | number; diff --git a/app/plugin/TableOperations.ts b/app/plugin/TableOperations.ts index b78d243a..3deb4eb5 100644 --- a/app/plugin/TableOperations.ts +++ b/app/plugin/TableOperations.ts @@ -4,22 +4,32 @@ import * as Types from 'app/plugin/DocApiTypes'; * Offer CRUD-style operations on a table. */ export interface TableOperations { - // Create a record or records. + /** + * Create a record or records. + */ create(records: Types.NewRecord, options?: OpOptions): Promise; create(records: Types.NewRecord[], options?: OpOptions): Promise; - // Update a record or records. + /** + * Update a record or records. + */ update(records: Types.Record|Types.Record[], options?: OpOptions): Promise; - // Delete a record or records. + /** + * Delete a record or records. + */ destroy(recordId: Types.RecordId): Promise; destroy(recordIds: Types.RecordId[]): Promise; - // Add or update a record or records. + /** + * Add or update a record or records. + */ upsert(records: Types.AddOrUpdateRecord|Types.AddOrUpdateRecord[], options?: UpsertOptions): Promise; - // Determine the tableId of the table. + /** + * Determine the tableId of the table. + */ getTableId(): Promise; // TODO: offer a way to query the table. @@ -32,7 +42,7 @@ export interface TableOperations { * This can be disabled. */ export interface OpOptions { - parseStrings?: boolean; + parseStrings?: boolean; /** whether to parse strings based on the column type. */ } /** @@ -40,8 +50,8 @@ export interface OpOptions { * onMany is first, and allowEmptyRequire is false. */ export interface UpsertOptions extends OpOptions { - add?: boolean; // permit inserting a record - update?: boolean; // permit updating a record - onMany?: 'none' | 'first' | 'all'; // whether to update none, one, or all matching records - allowEmptyRequire?: boolean; // allow "wildcard" operation + add?: boolean; /** permit inserting a record */ + update?: boolean; /** permit updating a record */ + onMany?: 'none' | 'first' | 'all'; /** whether to update none, one, or all matching records */ + allowEmptyRequire?: boolean; /** allow "wildcard" operation */ } diff --git a/app/plugin/grist-plugin-api.ts b/app/plugin/grist-plugin-api.ts index 09714681..c888a62a 100644 --- a/app/plugin/grist-plugin-api.ts +++ b/app/plugin/grist-plugin-api.ts @@ -46,46 +46,103 @@ export const rpc: Rpc = new Rpc({logger: createRpcLogger()}); export const api = rpc.getStub(RPC_GRISTAPI_INTERFACE, checkers.GristAPI); export const coreDocApi = rpc.getStub('GristDocAPI@grist', checkers.GristDocAPI); + +/** + * Interface for the records backing a custom widget. + */ export const viewApi = rpc.getStub('GristView', checkers.GristView); + +/** + * Interface for the state of a custom widget. + */ export const widgetApi = rpc.getStub('WidgetAPI', checkers.WidgetAPI); + +/** + * Interface for the mapping of a custom widget. + */ export const sectionApi = rpc.getStub('CustomSectionAPI', checkers.CustomSectionAPI); + +/** + * Shortcut for [[GristView.allowSelectBy]]. + */ export const allowSelectBy = viewApi.allowSelectBy; + +/** + * Shortcut for [[GristView.setSelectedRows]]. + */ export const setSelectedRows = viewApi.setSelectedRows; + +/** + * Fetches data backing the widget as for [[GristView.fetchSelectedTable]], + * but decoding data by default, replacing e.g. ['D', timestamp] with + * a moment date. Option `keepEncoded` skips the decoding step. + */ +export async function fetchSelectedTable(options: {keepEncoded?: boolean} = {}) { + const table = await viewApi.fetchSelectedTable(); + return options.keepEncoded ? table : + mapValues(table, (col) => col.map(decodeObject)); +} + +/** + * Fetches current selected record as for [[GristView.fetchSelectedRecord]], + * but decoding data by default, replacing e.g. ['D', timestamp] with + * a moment date. Option `keepEncoded` skips the decoding step. + */ +export async function fetchSelectedRecord(rowId: number, options: {keepEncoded?: boolean} = {}) { + const rec = await viewApi.fetchSelectedRecord(rowId); + return options.keepEncoded ? rec : + mapValues(rec, decodeObject); +} + + +/** + * A collection of methods for fetching document data. The + * fetchSelectedTable and fetchSelectedRecord methods are + * overridden to decode data by default. + */ export const docApi: GristDocAPI & GristView = { ...coreDocApi, ...viewApi, - - // Change fetchSelectedTable() to decode data by default, replacing e.g. ['D', timestamp] with - // a moment date. New option `keepEncoded` skips the decoding step. - async fetchSelectedTable(options: {keepEncoded?: boolean} = {}) { - const table = await viewApi.fetchSelectedTable(); - return options.keepEncoded ? table : - mapValues(table, (col) => col.map(decodeObject)); - }, - - // Change fetchSelectedRecord() to decode data by default, replacing e.g. ['D', timestamp] with - // a moment date. New option `keepEncoded` skips the decoding step. - async fetchSelectedRecord(rowId: number, options: {keepEncoded?: boolean} = {}) { - const rec = await viewApi.fetchSelectedRecord(rowId); - return options.keepEncoded ? rec : - mapValues(rec, decodeObject); - }, + fetchSelectedTable, + fetchSelectedRecord, }; export const on = rpc.on.bind(rpc); // Exposing widgetApi methods in a module scope. + +/** + * Shortcut for [[WidgetAPI.getOption]] + */ export const getOption = widgetApi.getOption.bind(widgetApi); + +/** + * Shortcut for [[WidgetAPI.setOption]] + */ export const setOption = widgetApi.setOption.bind(widgetApi); + +/** + * Shortcut for [[WidgetAPI.setOptions]] + */ export const setOptions = widgetApi.setOptions.bind(widgetApi); + +/** + * Shortcut for [[WidgetAPI.getOptions]] + */ export const getOptions = widgetApi.getOptions.bind(widgetApi); + +/** + * Shortcut for [[WidgetAPI.clearOptions]] + */ export const clearOptions = widgetApi.clearOptions.bind(widgetApi); -// Get access to a table in the document. If no tableId specified, this -// will use the current selected table (for custom widgets). -// If a table does not exist, there will be no error until an operation -// on the table is attempted. +/** + * Get access to a table in the document. If no tableId specified, this + * will use the current selected table (for custom widgets). + * If a table does not exist, there will be no error until an operation + * on the table is attempted. + */ export function getTable(tableId?: string): TableOperations { return new TableOperationsImpl({ async getTableId() { @@ -100,7 +157,9 @@ export function getTable(tableId?: string): TableOperations { }, {}); } -// Get the current selected table (for custom widgets). +/** + * Get the current selected table (for custom widgets). + */ export const selectedTable: TableOperations = getTable(); // Get the ID of the current selected table (for custom widgets). @@ -240,13 +299,15 @@ export function mapColumnNamesBack(data: any, options: { return mapColumnNames(data, {...options, reverse: true}); } -// For custom widgets, add a handler that will be called whenever the -// row with the cursor changes - either by switching to a different row, or -// by some value within the row potentially changing. Handler may -// in the future be called with null if the cursor moves away from -// any row. -// TODO: currently this will be called even if the content of a different row -// changes. +/** + * For custom widgets, add a handler that will be called whenever the + * row with the cursor changes - either by switching to a different row, or + * by some value within the row potentially changing. Handler may + * in the future be called with null if the cursor moves away from + * any row. + * TODO: currently this will be called even if the content of a different row + * changes. + */ export function onRecord(callback: (data: RowRecord | null, mappings: WidgetColumnMap | null) => unknown) { on('message', async function(msg) { if (!msg.tableId || !msg.rowId) { return; } @@ -254,8 +315,11 @@ export function onRecord(callback: (data: RowRecord | null, mappings: WidgetColu callback(rec, await getMappingsIfChanged(msg)); }); } -// For custom widgets, add a handler that will be called whenever the -// selected records change. Handler will be called with a list of records. + +/** + * For custom widgets, add a handler that will be called whenever the + * selected records change. Handler will be called with a list of records. + */ export function onRecords(callback: (data: RowRecord[], mappings: WidgetColumnMap | null) => unknown) { on('message', async function(msg) { if (!msg.tableId || !msg.dataChange) { return; } @@ -274,10 +338,13 @@ export function onRecords(callback: (data: RowRecord[], mappings: WidgetColumnMa } -// For custom widgets, add a handler that will be called whenever the -// widget options change (and on initial ready message). Handler will be -// called with an object containing save json options, or null if no options were saved. -// Second parameter +/** + * For custom widgets, add a handler that will be called whenever the + * widget options change (and on initial ready message). Handler will be + * called with an object containing saved json options, or null if no options were saved. + * The second parameter has information about the widgets relationship with + * the document that contains it. + */ export function onOptions(callback: (options: any, settings: InteractionOptions) => unknown) { on('message', async function(msg) { if (msg.settings) { @@ -297,6 +364,7 @@ export function onOptions(callback: (options: any, settings: InteractionOptions) * `name`. Calling `addImporter(...)` from another component than a `safeBrowser` component is not * currently supported. * + * @internal */ export async function addImporter(name: string, path: string, mode: 'fullscreen' | 'inline', options?: RenderOptions) { // checker is omitted for implementation because call was already checked by grist. @@ -315,7 +383,10 @@ export async function addImporter(name: string, path: string, mode: 'fullscreen' }); } -interface ReadyPayload extends Omit { +/** + * Options when initializing connection to Grist. + */ +export interface ReadyPayload extends Omit { /** * Handler that will be called by Grist to open additional configuration panel inside the Custom Widget. */ @@ -354,6 +425,7 @@ export function ready(settings?: ReadyPayload): void { })(); } +/** @internal */ function getPluginPath(location: Location) { return location.pathname.replace(/^\/plugins\//, ''); } @@ -393,6 +465,7 @@ if (typeof window !== 'undefined') { rpc.setSendMessage((data) => { return; }); } +/** @internal */ function createRpcLogger(): IRpcLogger { let prefix: string; if (typeof window !== 'undefined') { diff --git a/test/nbrowser/testServer.ts b/test/nbrowser/testServer.ts index 9f12bc0e..37c6d955 100644 --- a/test/nbrowser/testServer.ts +++ b/test/nbrowser/testServer.ts @@ -90,7 +90,7 @@ export class TestServerMerged implements IMochaServer { // logging. Server code uses a global logger, so it's hard to separate out (especially so if // we ever run different servers for different tests). const serverLog = process.env.VERBOSE ? 'inherit' : nodeLogFd; - const env = { + const env: Record = { TYPEORM_DATABASE: this._getDatabaseFile(), TEST_CLEAN_DATABASE: reset ? 'true' : '', GRIST_DATA_DIR: this.testDocDir, diff --git a/test/server/gristClient.ts b/test/server/gristClient.ts index 324a803a..f84fce87 100644 --- a/test/server/gristClient.ts +++ b/test/server/gristClient.ts @@ -70,7 +70,7 @@ export class GristClient { if (this._pending.length) { return this._pending.shift(); } - await new Promise(resolve => this._consumer = resolve); + await new Promise(resolve => this._consumer = resolve); } } diff --git a/tsconfig.json b/tsconfig.json index 77cad3d3..d2421771 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,14 @@ { "path": "./app" }, { "path": "./stubs/app" }, { "path": "./test" }, - ] + ], + "typedocOptions": { + "entryPoints": [ + "app/plugin/grist-plugin-api.ts", + "app/plugin/TableOperations.ts", + ], + "excludeInternal": "true", + "excludeNotDocumented": "true", + "out": "doc" + } }