diff --git a/README.md b/README.md index 00916af..af64cec 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ This app provides a **"Watch New Submissions"** instant trigger so Make users ca 3. Selects a workspace and form from dynamic dropdowns 4. Make auto-registers a webhook via OpnForm's standard integration API 5. On each form submission, OpnForm POSTs data to Make's webhook -6. When the scenario is deleted, Make auto-unregisters the webhook +6. When the webhook is deleted, Make auto-unregisters it from OpnForm ## Architecture @@ -26,15 +26,16 @@ This integration follows the same lightweight pattern as OpnForm's Activepieces │ └── communication.json # Auth validation (GET /open/workspaces) ├── webhook/ │ ├── parameters.json # Webhook parameters (form selection) +│ ├── communication.json # Convert the incoming request body to a Make bundle │ ├── attach.json # Register webhook (POST /open/forms/{id}/integrations) │ └── detach.json # Unregister webhook (DELETE /open/forms/{id}/integrations/{id}) ├── modules/ │ └── watchNewSubmissions/ -│ ├── communication.json # Incoming webhook data processing +│ ├── communication.json # Empty: the paired webhook already produces the bundle │ └── expect.json # Module parameters (form/workspace selectors) └── rpcs/ ├── listWorkspaces.json # Dynamic dropdown (GET /open/workspaces) - └── listForms.json # Dynamic dropdown (GET /open/workspaces/{id}/forms) with pagination + └── listForms.json # Dynamic dropdown (GET /open/workspaces/{id}/forms) with bounded pagination ``` ## API Endpoints Used @@ -44,7 +45,7 @@ All endpoints are part of OpnForm's existing API, authenticated via Bearer token | Endpoint | Method | Purpose | |---|---|---| | `/open/workspaces` | GET | Validate API key + list workspaces | -| `/open/workspaces/{id}/forms` | GET | List forms (paginated, 10/page) | +| `/open/workspaces/{id}/forms` | GET | List forms (paginated, up to 100/page) | | `/open/forms/{id}/integrations` | POST | Create Make integration (attach webhook) | | `/open/forms/{id}/integrations/{integrationId}` | DELETE | Remove integration (detach webhook) | diff --git a/modules/watchNewSubmissions/communication.json b/modules/watchNewSubmissions/communication.json index e314cca..0967ef4 100644 --- a/modules/watchNewSubmissions/communication.json +++ b/modules/watchNewSubmissions/communication.json @@ -1,9 +1 @@ -{ - "output": "{{body}}", - "respond": { - "status": 200, - "body": { - "ok": true - } - } -} +{} diff --git a/rpcs/listForms.json b/rpcs/listForms.json index 5b20e1e..a10140c 100644 --- a/rpcs/listForms.json +++ b/rpcs/listForms.json @@ -1,6 +1,9 @@ { "url": "/open/workspaces/{{parameters.workspaceId}}/forms", "method": "GET", + "qs": { + "per_page": 100 + }, "response": { "output": { "label": "{{item.title}}", diff --git a/tests/contracts.test.mjs b/tests/contracts.test.mjs index 945866f..4ec211e 100644 --- a/tests/contracts.test.mjs +++ b/tests/contracts.test.mjs @@ -22,14 +22,26 @@ test('production origin targets the real Make app', async () => { }) test('attached webhook persists and reuses the remote integration id', async () => { + const communication = await readJson('webhook/communication.json') const attach = await readJson('webhook/attach.json') const detach = await readJson('webhook/detach.json') + assert.equal(communication.output, '{{body}}') + assert.equal(communication.respond.status, 200) assert.equal(attach.response.data.externalHookId, '{{body.form_integration.id}}') + assert.equal(attach.response.data.formId, '{{parameters.formId}}') + assert.match(detach.url, /{{webhook\.formId}}/) assert.match(detach.url, /{{webhook\.externalHookId}}/) + assert.doesNotMatch(detach.url, /parameters\.formId/) assert.doesNotMatch(detach.url, /webhook\.data/) }) +test('instant trigger preserves the bundle produced by its webhook', async () => { + const communication = await readJson('modules/watchNewSubmissions/communication.json') + + assert.deepEqual(communication, {}) +}) + test('form RPC follows the Laravel resource pagination envelope', async () => { const rpc = await readJson('rpcs/listForms.json') const response = { @@ -43,7 +55,7 @@ test('form RPC follows the Laravel resource pagination envelope', async () => { assert.equal(rpc.pagination.condition, '{{body.links.next}}') assert.equal(response.links.next.endsWith('page=2'), true) assert.equal(response.next_page_url, undefined) - assert.equal(rpc.qs, undefined) + assert.equal(rpc.qs.per_page, 100) }) test('trigger interface exposes dynamic fields and optional edit link', async () => { diff --git a/webhook/attach.json b/webhook/attach.json index 4845a11..e4d7469 100644 --- a/webhook/attach.json +++ b/webhook/attach.json @@ -14,7 +14,8 @@ }, "response": { "data": { - "externalHookId": "{{body.form_integration.id}}" + "externalHookId": "{{body.form_integration.id}}", + "formId": "{{parameters.formId}}" } } } diff --git a/webhook/communication.json b/webhook/communication.json new file mode 100644 index 0000000..e314cca --- /dev/null +++ b/webhook/communication.json @@ -0,0 +1,9 @@ +{ + "output": "{{body}}", + "respond": { + "status": 200, + "body": { + "ok": true + } + } +} diff --git a/webhook/detach.json b/webhook/detach.json index abc2a47..0b8e61d 100644 --- a/webhook/detach.json +++ b/webhook/detach.json @@ -1,4 +1,4 @@ { - "url": "/open/forms/{{parameters.formId}}/integrations/{{webhook.externalHookId}}", + "url": "/open/forms/{{webhook.formId}}/integrations/{{webhook.externalHookId}}", "method": "DELETE" }