Refactor MeasuresPage to use Relay fragments

Replace the client-side grouped-by-category view (fetching 500 items)
with a flat table using server-side filtering and cursor-based
pagination. Colocate GraphQL queries, fragments, and mutations in the
component file per console CLAUDE.md conventions.

Backend changes add a category filter to the measure list endpoints
(GraphQL, MCP) and a new measureCategories field on Organization.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2026-03-18 16:00:40 +01:00
parent 69c418d1b7
commit 82f241628c
12 changed files with 459 additions and 215 deletions

View File

@@ -326,6 +326,41 @@ WHERE
return count, nil
}
func (m *Measures) LoadDistinctCategoriesByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
) ([]string, error) {
q := `
SELECT DISTINCT
category
FROM
measures
WHERE
%s
AND organization_id = @organization_id
ORDER BY
category ASC
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return nil, fmt.Errorf("cannot query measure categories: %w", err)
}
categories, err := pgx.CollectRows(rows, pgx.RowTo[string])
if err != nil {
return nil, fmt.Errorf("cannot collect measure categories: %w", err)
}
return categories, nil
}
func (m *Measures) LoadByOrganizationID(
ctx context.Context,
conn pg.Conn,

View File

@@ -20,22 +20,25 @@ import (
type (
MeasureFilter struct {
query *string
state *MeasureState
query *string
state *MeasureState
category *string
}
)
func NewMeasureFilter(query *string, state *MeasureState) *MeasureFilter {
func NewMeasureFilter(query *string, state *MeasureState, category *string) *MeasureFilter {
return &MeasureFilter{
query: query,
state: state,
query: query,
state: state,
category: category,
}
}
func (f *MeasureFilter) SQLArguments() pgx.NamedArgs {
return pgx.NamedArgs{
"query": f.query,
"state": f.state,
"query": f.query,
"state": f.state,
"category": f.category,
}
}
@@ -60,6 +63,15 @@ AND
ELSE
state = @state::mitigation_state
END
)
)
AND
(
CASE
WHEN @category::text IS NULL OR @category::text = '' THEN
TRUE
ELSE
category = @category::text
END
)
`
}