mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(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
This commit is contained in:
parent
cd8eac36b8
commit
f7fdfab6bf
@ -135,7 +135,7 @@ class WebhookExternalTable implements IExternalTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async fetchAll(): Promise<TableDataAction> {
|
public async fetchAll(): Promise<TableDataAction> {
|
||||||
const webhooks = await this._docApi.getWebhooks();
|
const webhooks = (await this._docApi.getWebhooks()).webhooks;
|
||||||
this._initalizeWebhookList(webhooks);
|
this._initalizeWebhookList(webhooks);
|
||||||
const indices = range(webhooks.length);
|
const indices = range(webhooks.length);
|
||||||
return ['TableData', this.name, indices.map(i => i + 1),
|
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
|
// brute force, on the assumption that there won't be many
|
||||||
// webhooks, or that "updating" something that hasn't actually
|
// webhooks, or that "updating" something that hasn't actually
|
||||||
// changed is not disruptive.
|
// changed is not disruptive.
|
||||||
const webhooks = await this._docApi.getWebhooks();
|
const webhooks = (await this._docApi.getWebhooks()).webhooks;
|
||||||
this._initalizeWebhookList(webhooks);
|
this._initalizeWebhookList(webhooks);
|
||||||
for (const webhook of webhooks) {
|
for (const webhook of webhooks) {
|
||||||
const values = _mapWebhookValues(webhook);
|
const values = _mapWebhookValues(webhook);
|
||||||
|
@ -4,6 +4,14 @@
|
|||||||
import * as t from "ts-interface-checker";
|
import * as t from "ts-interface-checker";
|
||||||
// tslint:disable:object-literal-key-quotes
|
// 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([], {
|
export const WebhookFields = t.iface([], {
|
||||||
"url": "string",
|
"url": "string",
|
||||||
"eventTypes": t.array(t.union(t.lit("add"), t.lit("update"))),
|
"eventTypes": t.array(t.union(t.lit("add"), t.lit("update"))),
|
||||||
@ -14,10 +22,6 @@ export const WebhookFields = t.iface([], {
|
|||||||
"memo": t.opt("string"),
|
"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 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'));
|
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"),
|
"memo": t.opt("string"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WebhookSubscribeCollection = t.iface([], {
|
export const WebhookSummaryCollection = t.iface([], {
|
||||||
"webhooks": t.array("Webhook"),
|
"webhooks": t.array("WebhookSummary"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WebhookSummary = t.iface([], {
|
export const WebhookSummary = t.iface([], {
|
||||||
@ -87,12 +91,13 @@ export const WebhookUsage = t.iface([], {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const exportedTypeSuite: t.ITypeSuite = {
|
const exportedTypeSuite: t.ITypeSuite = {
|
||||||
WebhookFields,
|
WebhookSubscribeCollection,
|
||||||
Webhook,
|
Webhook,
|
||||||
|
WebhookFields,
|
||||||
WebhookBatchStatus,
|
WebhookBatchStatus,
|
||||||
WebhookStatus,
|
WebhookStatus,
|
||||||
WebhookSubscribe,
|
WebhookSubscribe,
|
||||||
WebhookSubscribeCollection,
|
WebhookSummaryCollection,
|
||||||
WebhookSummary,
|
WebhookSummary,
|
||||||
WebhookUpdate,
|
WebhookUpdate,
|
||||||
WebhookPatch,
|
WebhookPatch,
|
||||||
|
@ -33,7 +33,9 @@ export interface WebhookSubscribe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface WebhookSummaryCollection {
|
||||||
|
webhooks: Array<WebhookSummary>;
|
||||||
|
}
|
||||||
export interface WebhookSummary {
|
export interface WebhookSummary {
|
||||||
id: string;
|
id: string;
|
||||||
fields: {
|
fields: {
|
||||||
|
@ -14,7 +14,12 @@ import {encodeQueryParams} from 'app/common/gutil';
|
|||||||
import {FullUser, UserProfile} from 'app/common/LoginSessionAPI';
|
import {FullUser, UserProfile} from 'app/common/LoginSessionAPI';
|
||||||
import {OrgPrefs, UserOrgPrefs, UserPrefs} from 'app/common/Prefs';
|
import {OrgPrefs, UserOrgPrefs, UserPrefs} from 'app/common/Prefs';
|
||||||
import * as roles from 'app/common/roles';
|
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 {addCurrentOrgToPath} from 'app/common/urlUtils';
|
||||||
import omitBy from 'lodash/omitBy';
|
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.
|
// Get users that are worth proposing to "View As" for access control purposes.
|
||||||
getUsersForViewAs(): Promise<PermissionDataWithExtraUsers>;
|
getUsersForViewAs(): Promise<PermissionDataWithExtraUsers>;
|
||||||
|
|
||||||
getWebhooks(): Promise<WebhookSummary[]>;
|
getWebhooks(): Promise<WebhookSummaryCollection>;
|
||||||
addWebhook(webhook: WebhookFields): Promise<{webhookId: string}>;
|
addWebhook(webhook: WebhookFields): Promise<{webhookId: string}>;
|
||||||
removeWebhook(webhookId: string, tableId: string): Promise<void>;
|
removeWebhook(webhookId: string, tableId: string): Promise<void>;
|
||||||
// Update webhook
|
// Update webhook
|
||||||
@ -915,7 +920,7 @@ export class DocAPIImpl extends BaseAPI implements DocAPI {
|
|||||||
return this.requestJson(`${this._url}/usersForViewAs`);
|
return this.requestJson(`${this._url}/usersForViewAs`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getWebhooks(): Promise<WebhookSummary[]> {
|
public async getWebhooks(): Promise<WebhookSummaryCollection> {
|
||||||
return this.requestJson(`${this._url}/webhooks`);
|
return this.requestJson(`${this._url}/webhooks`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,13 @@ import {fromTableDataAction, RowRecord, TableColValues, TableDataAction} from 'a
|
|||||||
import {StringUnion} from 'app/common/StringUnion';
|
import {StringUnion} from 'app/common/StringUnion';
|
||||||
import {MetaRowRecord} from 'app/common/TableData';
|
import {MetaRowRecord} from 'app/common/TableData';
|
||||||
import {CellDelta} from 'app/common/TabularDiff';
|
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 {decodeObject} from 'app/plugin/objtypes';
|
||||||
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||||
import {makeExceptionalDocSession} from 'app/server/lib/DocSession';
|
import {makeExceptionalDocSession} from 'app/server/lib/DocSession';
|
||||||
@ -246,7 +252,7 @@ export class DocTriggers {
|
|||||||
/**
|
/**
|
||||||
* Creates summary for all webhooks in the document.
|
* Creates summary for all webhooks in the document.
|
||||||
*/
|
*/
|
||||||
public async summary(): Promise<WebhookSummary[]> {
|
public async summary(): Promise<WebhookSummaryCollection> {
|
||||||
// Prepare some data we will use.
|
// Prepare some data we will use.
|
||||||
const docData = this._activeDoc.docData!;
|
const docData = this._activeDoc.docData!;
|
||||||
const triggersTable = docData.getMetaTable("_grist_Triggers");
|
const triggersTable = docData.getMetaTable("_grist_Triggers");
|
||||||
@ -254,7 +260,7 @@ export class DocTriggers {
|
|||||||
const getColId = docData.getMetaTable("_grist_Tables_column").getRowPropFunc("colId");
|
const getColId = docData.getMetaTable("_grist_Tables_column").getRowPropFunc("colId");
|
||||||
const getUrl = async (id: string) => (await this._getWebHook(id))?.url ?? '';
|
const getUrl = async (id: string) => (await this._getWebHook(id))?.url ?? '';
|
||||||
const getUnsubscribeKey = async (id: string) => (await this._getWebHook(id))?.unsubscribeKey ?? '';
|
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.
|
// Go through all triggers int the document that we have.
|
||||||
for (const t of triggersTable.getRecords()) {
|
for (const t of triggersTable.getRecords()) {
|
||||||
@ -291,10 +297,10 @@ export class DocTriggers {
|
|||||||
// Create some statics and status info.
|
// Create some statics and status info.
|
||||||
usage: await this._stats.getUsage(act.id, this._webHookEventQueue),
|
usage: await this._stats.getUsage(act.id, this._webHookEventQueue),
|
||||||
};
|
};
|
||||||
result.push(entry);
|
resultTable.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return {webhooks: resultTable};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWebhookTriggerRecord(webhookId: string) {
|
public getWebhookTriggerRecord(webhookId: string) {
|
||||||
|
@ -2800,9 +2800,23 @@ function testDocApi() {
|
|||||||
return resp.data;
|
return resp.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 () {
|
it("POST /docs/{did}/tables/{tid}/_subscribe validates inputs", async function () {
|
||||||
|
|
||||||
|
|
||||||
await oldSubscribeCheck({}, 400, /eventTypes is missing/);
|
await oldSubscribeCheck({}, 400, /eventTypes is missing/);
|
||||||
await oldSubscribeCheck({eventTypes: 0}, 400, /url is missing/, /eventTypes is not an array/);
|
await oldSubscribeCheck({eventTypes: 0}, 400, /url is missing/, /eventTypes is not an array/);
|
||||||
await oldSubscribeCheck({eventTypes: []}, 400, /url is missing/);
|
await oldSubscribeCheck({eventTypes: []}, 400, /url is missing/);
|
||||||
@ -2922,7 +2936,7 @@ function testDocApi() {
|
|||||||
async function getRegisteredWebhooks() {
|
async function getRegisteredWebhooks() {
|
||||||
const response = await axios.get(
|
const response = await axios.get(
|
||||||
`${serverUrl}/api/docs/${docIds.Timesheets}/webhooks`, chimpy);
|
`${serverUrl}/api/docs/${docIds.Timesheets}/webhooks`, chimpy);
|
||||||
return response.data;
|
return response.data.webhooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteWebhookCheck(webhookId: any) {
|
async function deleteWebhookCheck(webhookId: any) {
|
||||||
@ -3311,7 +3325,7 @@ function testDocApi() {
|
|||||||
`${serverUrl}/api/docs/${docId}/webhooks`, chimpy
|
`${serverUrl}/api/docs/${docId}/webhooks`, chimpy
|
||||||
);
|
);
|
||||||
assert.equal(result.status, 200);
|
assert.equal(result.status, 200);
|
||||||
return result.data;
|
return result.data.webhooks;
|
||||||
}
|
}
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
|
Loading…
Reference in New Issue
Block a user