(core) GET endpoint for webhooks returns now data in format {webhooks:[...]}

Summary:
Rework of endpoint GET  for webhooks to make it coherent with other endpoints. Now data should be return in {webhooks:[{id:"...",fields:{"..."}]} format

```
{
    "webhooks": [
        {
            "id": ...
            "fields": {
                "url": ...
                "unsubscribeKey": ...
                "eventTypes": [
                    "add",
                    "update"
                ],
                "isReadyColumn": null,
                "tableId": "...",
                "enabled": false,
                "name": "...",
                "memo": "..."
            },
            "usage": {
                "status": "idle",
                "numWaiting": 0,
                "lastEventBatch": null
            }
        },
        {
            "id": "...",
            "fields": {
                "url": "...",
                "unsubscribeKey": "...",
                "eventTypes": [
                    "add",
                    "update"
                ],
                "isReadyColumn": null,
                "tableId": "...",
                "enabled": true,
                "name": "...",
                "memo": "..."
            },
            "usage": {
                "status": "error",
                "numWaiting": 0,
                "updatedTime": 1689076978098,
                "lastEventBatch": {
                    "status": "rejected",
                    "httpStatus": 404,
                    "errorMessage": "{\"success\":false,\"error\":{\"message\":\"Alias 5a9bf6a8-4865-403a-bec6-b4ko not found\",\"id\":null}}",
                    "size": 49,
                    "attempts": 5
                },
                "lastSuccessTime": null,
                "lastFailureTime": 1689076978097,
                "lastErrorMessage": "{\"success\":false,\"error\":{\"message\":\"Alias 5a9bf6a8-4865-403a-bec6-b4ko not found\",\"id\":null}}",
                "lastHttpStatus": 404
            }
        }
    ]
}
```

Test Plan: new test added to check if GET data fromat is correct. Other tests fixed to handle changed endpoint.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D3966
pull/598/head
Jakub Serafin 10 months ago
parent cd8eac36b8
commit f7fdfab6bf

@ -135,7 +135,7 @@ class WebhookExternalTable implements IExternalTable {
}
public async fetchAll(): Promise<TableDataAction> {
const webhooks = await this._docApi.getWebhooks();
const webhooks = (await this._docApi.getWebhooks()).webhooks;
this._initalizeWebhookList(webhooks);
const indices = range(webhooks.length);
return ['TableData', this.name, indices.map(i => i + 1),
@ -222,7 +222,7 @@ class WebhookExternalTable implements IExternalTable {
// brute force, on the assumption that there won't be many
// webhooks, or that "updating" something that hasn't actually
// changed is not disruptive.
const webhooks = await this._docApi.getWebhooks();
const webhooks = (await this._docApi.getWebhooks()).webhooks;
this._initalizeWebhookList(webhooks);
for (const webhook of webhooks) {
const values = _mapWebhookValues(webhook);

@ -4,6 +4,14 @@
import * as t from "ts-interface-checker";
// tslint:disable:object-literal-key-quotes
export const WebhookSubscribeCollection = t.iface([], {
"webhooks": t.array("Webhook"),
});
export const Webhook = t.iface([], {
"fields": "WebhookFields",
});
export const WebhookFields = t.iface([], {
"url": "string",
"eventTypes": t.array(t.union(t.lit("add"), t.lit("update"))),
@ -14,10 +22,6 @@ export const WebhookFields = t.iface([], {
"memo": t.opt("string"),
});
export const Webhook = t.iface([], {
"fields": "WebhookFields",
});
export const WebhookBatchStatus = t.union(t.lit('success'), t.lit('failure'), t.lit('rejected'));
export const WebhookStatus = t.union(t.lit('idle'), t.lit('sending'), t.lit('retrying'), t.lit('postponed'), t.lit('error'), t.lit('invalid'));
@ -31,8 +35,8 @@ export const WebhookSubscribe = t.iface([], {
"memo": t.opt("string"),
});
export const WebhookSubscribeCollection = t.iface([], {
"webhooks": t.array("Webhook"),
export const WebhookSummaryCollection = t.iface([], {
"webhooks": t.array("WebhookSummary"),
});
export const WebhookSummary = t.iface([], {
@ -87,12 +91,13 @@ export const WebhookUsage = t.iface([], {
});
const exportedTypeSuite: t.ITypeSuite = {
WebhookFields,
WebhookSubscribeCollection,
Webhook,
WebhookFields,
WebhookBatchStatus,
WebhookStatus,
WebhookSubscribe,
WebhookSubscribeCollection,
WebhookSummaryCollection,
WebhookSummary,
WebhookUpdate,
WebhookPatch,

@ -33,7 +33,9 @@ export interface WebhookSubscribe {
}
export interface WebhookSummaryCollection {
webhooks: Array<WebhookSummary>;
}
export interface WebhookSummary {
id: string;
fields: {

@ -14,7 +14,12 @@ import {encodeQueryParams} from 'app/common/gutil';
import {FullUser, UserProfile} from 'app/common/LoginSessionAPI';
import {OrgPrefs, UserOrgPrefs, UserPrefs} from 'app/common/Prefs';
import * as roles from 'app/common/roles';
import {WebhookFields, WebhookSubscribe, WebhookSummary, WebhookUpdate} from 'app/common/Triggers';
import {
WebhookFields,
WebhookSubscribe,
WebhookSummaryCollection,
WebhookUpdate
} from 'app/common/Triggers';
import {addCurrentOrgToPath} from 'app/common/urlUtils';
import omitBy from 'lodash/omitBy';
@ -457,7 +462,7 @@ export interface DocAPI {
// Get users that are worth proposing to "View As" for access control purposes.
getUsersForViewAs(): Promise<PermissionDataWithExtraUsers>;
getWebhooks(): Promise<WebhookSummary[]>;
getWebhooks(): Promise<WebhookSummaryCollection>;
addWebhook(webhook: WebhookFields): Promise<{webhookId: string}>;
removeWebhook(webhookId: string, tableId: string): Promise<void>;
// Update webhook
@ -915,7 +920,7 @@ export class DocAPIImpl extends BaseAPI implements DocAPI {
return this.requestJson(`${this._url}/usersForViewAs`);
}
public async getWebhooks(): Promise<WebhookSummary[]> {
public async getWebhooks(): Promise<WebhookSummaryCollection> {
return this.requestJson(`${this._url}/webhooks`);
}

@ -8,7 +8,13 @@ import {fromTableDataAction, RowRecord, TableColValues, TableDataAction} from 'a
import {StringUnion} from 'app/common/StringUnion';
import {MetaRowRecord} from 'app/common/TableData';
import {CellDelta} from 'app/common/TabularDiff';
import {WebhookBatchStatus, WebhookStatus, WebhookSummary, WebhookUsage} from 'app/common/Triggers';
import {
WebhookBatchStatus,
WebhookStatus,
WebhookSummary,
WebhookSummaryCollection,
WebhookUsage
} from 'app/common/Triggers';
import {decodeObject} from 'app/plugin/objtypes';
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
import {makeExceptionalDocSession} from 'app/server/lib/DocSession';
@ -246,7 +252,7 @@ export class DocTriggers {
/**
* Creates summary for all webhooks in the document.
*/
public async summary(): Promise<WebhookSummary[]> {
public async summary(): Promise<WebhookSummaryCollection> {
// Prepare some data we will use.
const docData = this._activeDoc.docData!;
const triggersTable = docData.getMetaTable("_grist_Triggers");
@ -254,7 +260,7 @@ export class DocTriggers {
const getColId = docData.getMetaTable("_grist_Tables_column").getRowPropFunc("colId");
const getUrl = async (id: string) => (await this._getWebHook(id))?.url ?? '';
const getUnsubscribeKey = async (id: string) => (await this._getWebHook(id))?.unsubscribeKey ?? '';
const result: WebhookSummary[] = [];
const resultTable: WebhookSummary[] = [];
// Go through all triggers int the document that we have.
for (const t of triggersTable.getRecords()) {
@ -291,10 +297,10 @@ export class DocTriggers {
// Create some statics and status info.
usage: await this._stats.getUsage(act.id, this._webHookEventQueue),
};
result.push(entry);
resultTable.push(entry);
}
}
return result;
return {webhooks: resultTable};
}
public getWebhookTriggerRecord(webhookId: string) {

@ -2800,9 +2800,23 @@ function testDocApi() {
return resp.data;
}
it("POST /docs/{did}/tables/{tid}/_subscribe validates inputs", async function () {
it("GET /docs/{did}/webhooks retrieves a list of webhooks", async function () {
const registerResponse = await postWebhookCheck({webhooks:[{fields:{tableId: "Table1", eventTypes: ["add"], url: "https://example.com"}}]}, 200);
const resp = await axios.get(`${serverUrl}/api/docs/${docIds.Timesheets}/webhooks`, chimpy);
try{
assert.equal(resp.status, 200);
assert.isAtLeast(resp.data.webhooks.length, 1);
assert.containsAllKeys(resp.data.webhooks[0], ['id', 'fields']);
assert.containsAllKeys(resp.data.webhooks[0].fields,
['enabled', 'isReadyColumn', 'memo', 'name', 'tableId', 'eventTypes', 'url']);
}
finally{
//cleanup
await deleteWebhookCheck(registerResponse.webhooks[0].id);
}
});
it("POST /docs/{did}/tables/{tid}/_subscribe validates inputs", async function () {
await oldSubscribeCheck({}, 400, /eventTypes is missing/);
await oldSubscribeCheck({eventTypes: 0}, 400, /url is missing/, /eventTypes is not an array/);
await oldSubscribeCheck({eventTypes: []}, 400, /url is missing/);
@ -2922,7 +2936,7 @@ function testDocApi() {
async function getRegisteredWebhooks() {
const response = await axios.get(
`${serverUrl}/api/docs/${docIds.Timesheets}/webhooks`, chimpy);
return response.data;
return response.data.webhooks;
}
async function deleteWebhookCheck(webhookId: any) {
@ -3311,7 +3325,7 @@ function testDocApi() {
`${serverUrl}/api/docs/${docId}/webhooks`, chimpy
);
assert.equal(result.status, 200);
return result.data;
return result.data.webhooks;
}
before(async function () {

Loading…
Cancel
Save