mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Adding /webhooks endpoint
Summary: - New /webhooks event that lists all webhooks in a document (available for owners), - Monitoring webhooks usage and saving it in memory or Redis, - Loosening _usubscribe API endpoint, so that the information returned from the /webhook endpoint is enough to unsubscribe, - Owners can remove webhook without the unsubscribe key. The endpoint lists all webhooks that are registered in a document, not just webhooks from a single table. There are two status fields. First for the webhook, second for the last request attempt. Webhook can have 5 statuses: 'idle', 'sending', 'retrying', 'postponed', 'error', which roughly describes what the sendLoop is currently doing. The 'error' status describes a situation when all request attempts failed and the queue needs to be drained, so some requests were dropped. The last request status can only be: 'success', 'failure' or 'rejected'. Rejected means that the last batch was dropped because the queue was too long. Test Plan: New and updated tests Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3727
This commit is contained in:
@@ -589,31 +589,32 @@ export class DocWorkerApi {
|
||||
withDoc(async (activeDoc, req, res) => {
|
||||
const metaTables = await getMetaTables(activeDoc, req);
|
||||
const tableRef = tableIdToRef(metaTables, req.params.tableId);
|
||||
const {triggerId, unsubscribeKey, webhookId} = req.body;
|
||||
const {unsubscribeKey, webhookId} = req.body as WebhookSubscription;
|
||||
|
||||
// Validate combination of triggerId, webhookId, and tableRef.
|
||||
// This is overly strict, webhookId should be enough,
|
||||
// but it should be easy to relax that later if we want.
|
||||
const [, , triggerRowIds, triggerColData] = metaTables._grist_Triggers;
|
||||
const triggerRowIndex = triggerRowIds.indexOf(triggerId);
|
||||
const triggerRowIndex = triggerColData.actions.findIndex(a => {
|
||||
const actions: any[] = JSON.parse((a || '[]') as string);
|
||||
return actions.some(action => action.id === webhookId && action?.type === "webhook");
|
||||
});
|
||||
if (triggerRowIndex === -1) {
|
||||
throw new ApiError(`Trigger not found "${triggerId}"`, 404);
|
||||
throw new ApiError(`Webhook not found "${webhookId || ''}"`, 404);
|
||||
}
|
||||
if (triggerColData.tableRef[triggerRowIndex] !== tableRef) {
|
||||
throw new ApiError(`Wrong table`, 400);
|
||||
}
|
||||
const actions = JSON.parse(triggerColData.actions[triggerRowIndex] as string);
|
||||
if (!_.find(actions, {type: "webhook", id: webhookId})) {
|
||||
throw new ApiError(`Webhook not found "${webhookId}"`, 404);
|
||||
}
|
||||
const triggerRowId = triggerRowIds[triggerRowIndex];
|
||||
|
||||
const checkKey = !(await this._isOwner(req));
|
||||
// Validate unsubscribeKey before deleting trigger from document
|
||||
await this._dbManager.removeWebhook(webhookId, activeDoc.docName, unsubscribeKey);
|
||||
await this._dbManager.removeWebhook(webhookId, activeDoc.docName, unsubscribeKey, checkKey);
|
||||
|
||||
// TODO handle trigger containing other actions when that becomes possible
|
||||
await handleSandboxError("_grist_Triggers", [], activeDoc.applyUserActions(
|
||||
docSessionFromRequest(req),
|
||||
[['RemoveRecord', "_grist_Triggers", triggerId]]));
|
||||
[['RemoveRecord', "_grist_Triggers", triggerRowId]]));
|
||||
|
||||
res.json({success: true});
|
||||
})
|
||||
@@ -627,6 +628,13 @@ export class DocWorkerApi {
|
||||
})
|
||||
);
|
||||
|
||||
// Lists all webhooks and their current status in the document.
|
||||
this._app.get('/api/docs/:docId/webhooks', isOwner,
|
||||
withDoc(async (activeDoc, req, res) => {
|
||||
res.json(await activeDoc.webhooksSummary());
|
||||
})
|
||||
);
|
||||
|
||||
// Reload a document forcibly (in fact this closes the doc, it will be automatically
|
||||
// reopened on use).
|
||||
this._app.post('/api/docs/:docId/force-reload', canEdit, throttled(async (req, res) => {
|
||||
@@ -1429,3 +1437,8 @@ export function getDocApiUsageKeysToIncr(
|
||||
}
|
||||
// Usage exceeded all the time buckets, so return undefined to reject the request.
|
||||
}
|
||||
|
||||
export interface WebhookSubscription {
|
||||
unsubscribeKey: string;
|
||||
webhookId: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user