isolate watchedColIds string in frontend

pull/832/head
CamilleLegeron 3 months ago
parent cca6541a20
commit c73e6957f4

@ -61,7 +61,7 @@ const WEBHOOK_COLUMNS = [
},
{
id: 'vt_webhook_fc10',
colId: 'columnIds',
colId: 'watchedColIdsText',
type: 'Text',
label: t('Filter for changes in these columns (semicolon-separated ids)'),
},
@ -113,7 +113,7 @@ const WEBHOOK_VIEW_FIELDS: Array<(typeof WEBHOOK_COLUMNS)[number]['colId']> = [
'name', 'memo',
'eventTypes', 'url',
'tableId', 'isReadyColumn',
'columnIds', 'webhookId',
'watchedColIdsText', 'webhookId',
'enabled', 'status'
];
@ -133,9 +133,9 @@ class WebhookExternalTable implements IExternalTable {
public name = 'GristHidden_WebhookTable';
public initialActions = _prepareWebhookInitialActions(this.name);
public saveableFields = [
'tableId', 'columnIds', 'url', 'eventTypes', 'enabled', 'name', 'memo', 'isReadyColumn',
'tableId', 'watchedColIdsText', 'url', 'eventTypes', 'enabled', 'name', 'memo', 'isReadyColumn',
];
public webhooks: ObservableArray<WebhookSummary> = observableArray<WebhookSummary>([]);
public webhooks: ObservableArray<WebhookPageSummary> = observableArray<WebhookPageSummary>([]);
public constructor(private _docApi: DocAPI) {
}
@ -276,7 +276,12 @@ class WebhookExternalTable implements IExternalTable {
private _initalizeWebhookList(webhooks: WebhookSummary[]){
this.webhooks.removeAll();
this.webhooks.push(...webhooks);
this.webhooks.push(
...webhooks.map(webhook => {
const uiWebhook: WebhookPageSummary = {...webhook};
uiWebhook.fields.watchedColIdsText = webhook.fields.watchedColIds ? webhook.fields.watchedColIds.join(";") : "";
return uiWebhook;
}));
}
private _getErrorString(e: ApiError): string {
@ -315,6 +320,9 @@ class WebhookExternalTable implements IExternalTable {
if (fields.eventTypes) {
fields.eventTypes = without(fields.eventTypes, 'L');
}
fields.watchedColIds = fields.watchedColIdsText
? fields.watchedColIdsText.split(";").filter((colId: string) => colId.trim() !== "")
: [];
return fields;
}
}
@ -447,16 +455,21 @@ function _prepareWebhookInitialActions(tableId: string): DocAction[] {
/**
* Map a webhook summary to a webhook table raw record. The main
* difference is that `eventTypes` is tweaked to be in a cell format,
* and `status` is converted to a string.
* `status` is converted to a string,
* and `watchedColIdsText` is converted to list in a cell format.
*/
function _mapWebhookValues(webhookSummary: WebhookSummary): Partial<WebhookSchemaType> {
function _mapWebhookValues(webhookSummary: WebhookPageSummary): Partial<WebhookSchemaType> {
const fields = webhookSummary.fields;
const {eventTypes} = fields;
const {eventTypes, watchedColIdsText} = fields;
const watchedColIds = watchedColIdsText
? watchedColIdsText.split(";").filter(colId => colId.trim() !== "")
: [];
return {
...fields,
webhookId: webhookSummary.id,
status: JSON.stringify(webhookSummary.usage),
eventTypes: [GristObjCode.List, ...eventTypes],
watchedColIds: [GristObjCode.List, ...watchedColIds],
};
}
@ -464,6 +477,11 @@ type WebhookSchemaType = {
[prop in keyof WebhookSummary['fields']]: WebhookSummary['fields'][prop]
} & {
eventTypes: [GristObjCode, ...unknown[]];
watchedColIds: [GristObjCode, ...unknown[]];
status: string;
webhookId: string;
}
type WebhookPageSummary = WebhookSummary & {
fields: {watchedColIdsText?: string;}
}

@ -16,7 +16,7 @@ export const WebhookFields = t.iface([], {
"url": "string",
"eventTypes": t.array(t.union(t.lit("add"), t.lit("update"))),
"tableId": "string",
"columnIds": "string",
"watchedColIds": t.array("string"),
"enabled": t.opt("boolean"),
"isReadyColumn": t.opt(t.union("string", "null")),
"name": t.opt("string"),
@ -30,7 +30,7 @@ export const WebhookStatus = t.union(t.lit('idle'), t.lit('sending'), t.lit('ret
export const WebhookSubscribe = t.iface([], {
"url": "string",
"eventTypes": t.array(t.union(t.lit("add"), t.lit("update"))),
"columnIds": t.opt("string"),
"watchedColIds": t.array("string"),
"enabled": t.opt("boolean"),
"isReadyColumn": t.opt(t.union("string", "null")),
"name": t.opt("string"),
@ -49,7 +49,7 @@ export const WebhookSummary = t.iface([], {
"eventTypes": t.array("string"),
"isReadyColumn": t.union("string", "null"),
"tableId": "string",
"columnIds": "string",
"watchedColIds": t.array("string"),
"enabled": "boolean",
"name": "string",
"memo": "string",
@ -66,7 +66,7 @@ export const WebhookPatch = t.iface([], {
"url": t.opt("string"),
"eventTypes": t.opt(t.array(t.union(t.lit("add"), t.lit("update")))),
"tableId": t.opt("string"),
"columnIds": t.opt("string"),
"watchedColIds": t.array("string"),
"enabled": t.opt("boolean"),
"isReadyColumn": t.opt(t.union("string", "null")),
"name": t.opt("string"),

@ -10,7 +10,7 @@ export interface WebhookFields {
url: string;
eventTypes: Array<"add"|"update">;
tableId: string;
columnIds: string;
watchedColIds: string[];
enabled?: boolean;
isReadyColumn?: string|null;
name?: string;
@ -27,7 +27,7 @@ export type WebhookStatus = 'idle'|'sending'|'retrying'|'postponed'|'error'|'inv
export interface WebhookSubscribe {
url: string;
eventTypes: Array<"add"|"update">;
columnIds?: string;
watchedColIds: string[];
enabled?: boolean;
isReadyColumn?: string|null;
name?: string;
@ -46,7 +46,7 @@ export interface WebhookSummary {
eventTypes: string[];
isReadyColumn: string|null;
tableId: string;
columnIds: string;
watchedColIds: string[];
enabled: boolean;
name: string;
memo: string;
@ -66,7 +66,7 @@ export interface WebhookPatch {
url?: string;
eventTypes?: Array<"add"|"update">;
tableId?: string;
columnIds?: string;
watchedColIds: string[];
enabled?: boolean;
isReadyColumn?: string|null;
name?: string;

@ -380,7 +380,7 @@ export class DocWorkerApi {
const tablesTable = activeDoc.docData!.getMetaTable("_grist_Tables");
const trigger = webhookId ? activeDoc.triggers.getWebhookTriggerRecord(webhookId) : undefined;
let currentTableId = trigger ? tablesTable.getValue(trigger.tableRef, 'tableId')! : undefined;
const {url, eventTypes, columnIds, isReadyColumn, name} = webhook;
const {url, eventTypes, watchedColIds, isReadyColumn, name} = webhook;
const tableId = await getRealTableId(req.params.tableId || webhook.tableId, {metaTables});
const fields: Partial<SchemaTypes['_grist_Triggers']> = {};
@ -397,19 +397,18 @@ export class DocWorkerApi {
}
if (tableId !== undefined) {
if (columnIds) {
if (watchedColIds) {
if (tableId !== currentTableId && currentTableId) {
// if the tableId changed, we need to reset the columnIds
// if the tableId changed, we need to reset the watchedColIds
fields.watchedColRefList = [GristObjCode.List];
} else {
if (!tableId) {
throw new ApiError(`Cannot find columns "${columnIds}" because table is not known`, 404);
throw new ApiError(`Cannot find columns "${watchedColIds}" because table is not known`, 404);
}
// columnIds have to be of shape "columnId; columnId; columnId"
fields.watchedColRefList = [GristObjCode.List, ...columnIds.split(";")
.filter(columnId => columnId.trim() !== "")
fields.watchedColRefList = [GristObjCode.List, ...watchedColIds
.filter(colId => colId.trim() !== "")
.map(
columnId => { return colIdToReference(metaTables, tableId, columnId.trim().replace(/^\$/, '')); }
colId => { return colIdToReference(metaTables, tableId, colId.trim().replace(/^\$/, '')); }
)];
}
} else {

@ -277,6 +277,7 @@ export class DocTriggers {
// Webhook might have been deleted in the mean time.
continue;
}
const decodedWatchedColRefList = decodeObject(t.watchedColRefList) as number[] || [];
// Report some basic info and usage stats.
const entry: WebhookSummary = {
// Id of the webhook
@ -288,7 +289,7 @@ export class DocTriggers {
// Other fields used to register this webhook.
eventTypes: decodeObject(t.eventTypes) as string[],
isReadyColumn: getColId(t.isReadyColRef) ?? null,
columnIds: t.watchedColRefList?.slice(1).map(columnRef => getColId(columnRef as number)).join("; ") || "",
watchedColIds: decodedWatchedColRefList.map((columnRef) => getColId(columnRef)),
tableId: getTableId(t.tableRef) ?? null,
// For future use - for now every webhook is enabled.
enabled: t.enabled,

@ -34,7 +34,7 @@ describe('WebhookOverflow', function () {
enabled: true,
name: 'test webhook',
tableId: 'Table2',
columnIds: ''
watchedColIds: []
};
await docApi.addWebhook(webhookDetails);
await docApi.addWebhook(webhookDetails);

@ -1226,8 +1226,8 @@ function testDocApi() {
const listColResp = await axios.get(url, { ...chimpy, params: { hidden: true } });
assert.equal(listColResp.status, 200, "Should succeed in listing columns");
const columnIds = listColResp.data.columns.map(({id}: {id: string}) => id).sort();
assert.deepEqual(columnIds, ["B", "C", "manualSort"]);
const watchedColIds = listColResp.data.columns.map(({id}: {id: string}) => id).sort();
assert.deepEqual(watchedColIds, ["B", "C", "manualSort"]);
});
it('should return 404 if table not found', async function() {
@ -3441,7 +3441,7 @@ function testDocApi() {
await postWebhookCheck({
webhooks: [{
fields: {
tableId: "Table1", eventTypes: ["update"], columnIds: "notExisting",
tableId: "Table1", eventTypes: ["update"], watchedColIds: ["notExisting"],
url: `${serving.url}/200`
}
}]
@ -3871,7 +3871,7 @@ function testDocApi() {
tableId?: string,
isReadyColumn?: string | null,
eventTypes?: string[]
columnIds?: string,
watchedColIds?: string[],
}) {
// Subscribe helper that returns a method to unsubscribe.
const data = await subscribe(endpoint, docId, options);
@ -3889,7 +3889,7 @@ function testDocApi() {
tableId?: string,
isReadyColumn?: string|null,
eventTypes?: string[],
columnIds?: string,
watchedColIds?: string[],
name?: string,
memo?: string,
enabled?: boolean,
@ -3901,7 +3901,7 @@ function testDocApi() {
eventTypes: options?.eventTypes ?? ['add', 'update'],
url: `${serving.url}/${endpoint}`,
isReadyColumn: options?.isReadyColumn === undefined ? 'B' : options?.isReadyColumn,
...pick(options, 'name', 'memo', 'enabled', 'columnIds'),
...pick(options, 'name', 'memo', 'enabled', 'watchedColIds'),
}, chimpy
);
assert.equal(status, 200);
@ -4425,7 +4425,7 @@ function testDocApi() {
await webhook1();
});
it("should call to a webhook only when columns updated are in columnIds if not empty", async () => { // eslint-disable-line max-len
it("should call to a webhook only when columns updated are in watchedColIds if not empty", async () => { // eslint-disable-line max-len
// Create a test document.
const ws1 = (await userApi.getOrgWorkspaces('current'))[0].id;
const docId = await userApi.newDoc({ name: 'testdoc5' }, ws1);
@ -4449,9 +4449,9 @@ function testDocApi() {
await successCalled.waitAndReset();
};
// Webhook with only one columnId.
// Webhook with only one watchedColId.
const webhook1 = await autoSubscribe('200', docId, {
columnIds: 'A', eventTypes: ['add', 'update']
watchedColIds: ['A'], eventTypes: ['add', 'update']
});
successCalled.reset();
// Create record, that will call the webhook.
@ -4469,9 +4469,9 @@ function testDocApi() {
await assertSuccessCalled();
await webhook1(); // Unsubscribe.
// Webhook with multiple columnIds (check the shape of the columnIds string)
// Webhook with multiple watchedColIds
const webhook2 = await autoSubscribe('200', docId, {
columnIds: 'A; B', eventTypes: ['update']
watchedColIds: ['A', 'B'], eventTypes: ['update']
});
successCalled.reset();
await modifyColumn({ C: 'c3' });
@ -4480,9 +4480,9 @@ function testDocApi() {
await assertSuccessCalled();
await webhook2();
// Check that string terminating with ";" not breaking the webhook
// Check that empty string in watchedColIds are ignored
const webhook3 = await autoSubscribe('200', docId, {
columnIds: 'A;', eventTypes: ['update']
watchedColIds: ['A', ""], eventTypes: ['update']
});
await modifyColumn({ C: 'c4' });
await assertSuccessNotCalled();
@ -4511,7 +4511,7 @@ function testDocApi() {
tableId: 'Table1',
name: '',
memo: '',
columnIds: '',
watchedColIds: [],
}, usage : {
status: 'idle',
numWaiting: 0,
@ -4529,7 +4529,7 @@ function testDocApi() {
tableId: 'Table1',
name: '',
memo: '',
columnIds: '',
watchedColIds: [],
}, usage : {
status: 'idle',
numWaiting: 0,
@ -4870,7 +4870,7 @@ function testDocApi() {
isReadyColumn: 'B',
name: 'My Webhook',
memo: 'Sync store',
columnIds: 'A'
watchedColIds: ['A']
};
// subscribe
@ -4895,7 +4895,7 @@ function testDocApi() {
enabled: true,
name: 'My Webhook',
memo: 'Sync store',
columnIds: 'A',
watchedColIds: ['A'],
};
let stats = await readStats(docId);
@ -4946,11 +4946,11 @@ function testDocApi() {
// changing table without changing the ready column should reset the latter
await check({tableId: 'Table2'}, 200, '', expectedFields => {
expectedFields.isReadyColumn = null;
expectedFields.columnIds = "";
expectedFields.watchedColIds = [];
});
await check({tableId: 'Santa'}, 404, `Table not found "Santa"`);
await check({tableId: 'Table2', isReadyColumn: 'Foo', columnIds: ""}, 200);
await check({tableId: 'Table2', isReadyColumn: 'Foo', watchedColIds: [""]}, 200);
await check({eventTypes: ['add', 'update']}, 200);
await check({eventTypes: []}, 400, "eventTypes must be a non-empty array");

Loading…
Cancel
Save