Goal
Build PR 1 of map #160: the backend slim list for workflows and email templates. It is additive. Users see no change. MCP workflows-list gains fields and filters.
Contract: the spec, section "PR 1: backend slim list". The spec wins over this summary wherever they differ.
- Branch:
workflows-list-v2/slim-list (from origin/master)
- PR title:
feat(workflows): add slim summary list for workflows and email templates
- Terminal: a babysat draft PR on
PostHog/posthog, per the map's Notes. Never mark it ready, enqueue or merge.
- PR 2 (frontend search bar) stacks on this PR with
gh stack.
- Skills:
/tdd, /improving-drf-endpoints, /implementing-mcp-tools, /writing-tests, /writing-dataclasses, /writing-code-comments, /writing-pr-descriptions, /reviewing-with-coderabbit, /running-ci-preflight, /babysit-pr.
test-new-events-schema label: not needed (no event ingestion or event reads).
Acceptance criteria
GET /api/projects/:id/hog_flows/summaries/ (operation hog_flows_summaries_list) returns paginated HogFlowListRow rows with exactly the fields in the spec: id, name, description, status, type, origin_product, trigger_type, has_draft, channels, dispatches, email_steps, created_by, created_at, updated_at, user_access_level, last_7_days. email_steps[] carries action_id, name, subject, from_addresses, from_name, from_integration_ids, template_uuid.
GET /api/projects/:id/messaging_templates/summaries/ (operation messaging_templates_summaries_list) returns MessageTemplateListRow rows: id, name, description, type, subject, from_addresses, created_by, created_at, updated_at.
- Neither row carries
actions, edges, draft, inputs, secrets, the trigger config, or any email body (html, text, design, preheader).
- Pagination: default 500, max 1000, order
-updated_at, -id.
row.type comes from one helper shared with workflow_type_q. trigger_type reads the trigger action, with the legacy trigger column as fallback.
- From addresses resolve through one team-scoped
Integration query per request: override first, then each rotation integration, then a legacy string from.
last_7_days comes from one fetch_app_metric_totals_by_source call per request, and is null on every row (HTTP 200) when ClickHouse fails. Only summaries returns it.
- The
summaries action applies filter_queryset_by_access_level explicitly, because _filter_queryset_by_access_level only runs for list. summaries is a declared read action on both viewsets, so a personal API key with hog_flow:read can call it.
- Server filters on both
list and summaries: status and created_by take comma lists; new trigger_type and channel; exclude_status, exclude_type, exclude_trigger_type, exclude_created_by, exclude_channel. OR within a param, AND across, unknown values give 400. search, trigger, type, origin_product, broadcast_eligible, id, created_at, updated_at keep working as today. No string-built SQL: allowlisted enums or bound parameters only.
- Only the two
summaries paths are on GZIP_RESPONSE_ALLOW_LIST. The full hog_flows/ list is not.
- MCP
workflows-list keeps its operation. HogFlowSummarySerializer gains type, trigger_type, has_draft, channels, email_steps, and still omits actions, edges and draft. The tool description in products/workflows/mcp/tools.yaml names the new filters and fields.
hogli build:openapi is run, and the regenerated frontend and MCP code is committed. Every new field and parameter has help_text or a description.
- The query count is constant in the row count. A row for a workflow with a 20 KB email body is under 1.5 KB.
products/workflows/CONTRIBUTING.md describes the new endpoint and filters.
Test-first plan (outside-in)
Start with a failing API test for the summaries row shape, then the filters, then work inward to the pure summary function. The spec's test table lists every test and the regression it catches. In short:
test_hog_flow_summaries.py: exact row JSON for a mixed fixture (email override sender, email with sender rotation, SMS, Slack, webhook).
- Secret and body strings never appear in
summaries or in the MCP list.
- Row size under 1.5 KB with a 20 KB body.
assertNumQueries equal at 2 and 20 rows, on both endpoints.
type parity with ?type=; trigger_type follows the trigger action.
- Sender resolution cases, including another team's integration id.
last_7_days from a mocked totals function; null and 200 when it raises.
- An inaccessible workflow is absent from
summaries and from its totals; another team's rows are absent.
- Pagination cap and stability.
- Parameterized filter block over
list and summaries (multi-value, every exclude_*, 400s, existing params unchanged).
- gzip on
summaries, and none on hog_flows/.
- MCP list gains the fields and still has no graph.
- Email template summaries: shape, deleted and other-team rows excluded, no content, more than 100 templates page correctly.
Invented data only (example.com addresses, made-up names).
Proof bundle
- The red run of the first API tests, then green.
hogli test output for the new and touched test files, and hogli ci:preflight --strict passing.
semgrep --config .semgrep/rules/security/ . clean on the diff.
- An OpenAPI diff summary: the new operations, the new parameters on
hog_flows_list.
- A local timing of
summaries for 1,000 generated workflows with one 20 KB email step each, with and without gzip (invented fixture, local numbers only).
- The reviews the map requires: CodeRabbit, then adversarial, code-quality and logic/safety reviews, each by a fresh Opus sub-agent. Then
/babysit-pr until CI is green and every comment is addressed.
Out of scope
Server filters for sends, from, owner, health and kind; a stored list_summary column; the metrics/global meaning fix; any frontend change; phases 2 to 6.
Goal
Build PR 1 of map #160: the backend slim list for workflows and email templates. It is additive. Users see no change. MCP
workflows-listgains fields and filters.Contract: the spec, section "PR 1: backend slim list". The spec wins over this summary wherever they differ.
workflows-list-v2/slim-list(fromorigin/master)feat(workflows): add slim summary list for workflows and email templatesPostHog/posthog, per the map's Notes. Never mark it ready, enqueue or merge.gh stack./tdd,/improving-drf-endpoints,/implementing-mcp-tools,/writing-tests,/writing-dataclasses,/writing-code-comments,/writing-pr-descriptions,/reviewing-with-coderabbit,/running-ci-preflight,/babysit-pr.test-new-events-schemalabel: not needed (no event ingestion or event reads).Acceptance criteria
GET /api/projects/:id/hog_flows/summaries/(operationhog_flows_summaries_list) returns paginatedHogFlowListRowrows with exactly the fields in the spec:id, name, description, status, type, origin_product, trigger_type, has_draft, channels, dispatches, email_steps, created_by, created_at, updated_at, user_access_level, last_7_days.email_steps[]carriesaction_id, name, subject, from_addresses, from_name, from_integration_ids, template_uuid.GET /api/projects/:id/messaging_templates/summaries/(operationmessaging_templates_summaries_list) returnsMessageTemplateListRowrows:id, name, description, type, subject, from_addresses, created_by, created_at, updated_at.actions,edges,draft,inputs, secrets, the trigger config, or any email body (html,text,design,preheader).-updated_at, -id.row.typecomes from one helper shared withworkflow_type_q.trigger_typereads the trigger action, with the legacytriggercolumn as fallback.Integrationquery per request: override first, then each rotation integration, then a legacy stringfrom.last_7_dayscomes from onefetch_app_metric_totals_by_sourcecall per request, and isnullon every row (HTTP 200) when ClickHouse fails. Onlysummariesreturns it.summariesaction appliesfilter_queryset_by_access_levelexplicitly, because_filter_queryset_by_access_levelonly runs forlist.summariesis a declared read action on both viewsets, so a personal API key withhog_flow:readcan call it.listandsummaries:statusandcreated_bytake comma lists; newtrigger_typeandchannel;exclude_status,exclude_type,exclude_trigger_type,exclude_created_by,exclude_channel. OR within a param, AND across, unknown values give 400.search,trigger,type,origin_product,broadcast_eligible,id,created_at,updated_atkeep working as today. No string-built SQL: allowlisted enums or bound parameters only.summariespaths are onGZIP_RESPONSE_ALLOW_LIST. The fullhog_flows/list is not.workflows-listkeeps its operation.HogFlowSummarySerializergainstype, trigger_type, has_draft, channels, email_steps, and still omitsactions,edgesanddraft. The tool description inproducts/workflows/mcp/tools.yamlnames the new filters and fields.hogli build:openapiis run, and the regenerated frontend and MCP code is committed. Every new field and parameter hashelp_textor a description.products/workflows/CONTRIBUTING.mddescribes the new endpoint and filters.Test-first plan (outside-in)
Start with a failing API test for the
summariesrow shape, then the filters, then work inward to the pure summary function. The spec's test table lists every test and the regression it catches. In short:test_hog_flow_summaries.py: exact row JSON for a mixed fixture (email override sender, email with sender rotation, SMS, Slack, webhook).summariesor in the MCP list.assertNumQueriesequal at 2 and 20 rows, on both endpoints.typeparity with?type=;trigger_typefollows the trigger action.last_7_daysfrom a mocked totals function;nulland 200 when it raises.summariesand from its totals; another team's rows are absent.listandsummaries(multi-value, everyexclude_*, 400s, existing params unchanged).summaries, and none onhog_flows/.Invented data only (
example.comaddresses, made-up names).Proof bundle
hogli testoutput for the new and touched test files, andhogli ci:preflight --strictpassing.semgrep --config .semgrep/rules/security/ .clean on the diff.hog_flows_list.summariesfor 1,000 generated workflows with one 20 KB email step each, with and without gzip (invented fixture, local numbers only)./babysit-pruntil CI is green and every comment is addressed.Out of scope
Server filters for
sends,from,owner,healthandkind; a storedlist_summarycolumn; themetrics/globalmeaning fix; any frontend change; phases 2 to 6.