Compare commits
2 commits
main
...
fix/bug-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73c5f31f97 | ||
|
|
5ea3f287d3 |
2 changed files with 138 additions and 0 deletions
99
migrations/13_inc_search_fn.sql
Normal file
99
migrations/13_inc_search_fn.sql
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
-- 13_inc_search_fn.sql — fleettickets · ticket explorer search read-API
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- reporting.fn_inc_search powers the FleetOps "Ticket explorer" — an ad-hoc query
|
||||
-- over tickets by id / engineer / cluster / status / state / time, for historical
|
||||
-- and current tracking ("who closed what, where, when"). Backs GET /webhook/inc-search.
|
||||
--
|
||||
-- Source = tickets.inc (accumulates every ticket, never deleted → historical closures
|
||||
-- AND current open), using the generated columns from migration 03. owner is
|
||||
-- case-normalized (initcap(lower(...)), like migration 12) so an engineer isn't split
|
||||
-- across casings. All filters optional and AND-combined; results capped at p_limit.
|
||||
--
|
||||
-- state = 'closed' (default) → not-actionable, closed_at within [p_from, p_to)
|
||||
-- (bounds optional → all-time)
|
||||
-- 'open' → currently actionable (time ignored — open = live)
|
||||
-- 'all' → open OR closed-in-range
|
||||
--
|
||||
-- Returns { count, truncated, rows: [...] }. Idempotent (CREATE OR REPLACE).
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
SET search_path = tickets, public;
|
||||
|
||||
CREATE OR REPLACE FUNCTION reporting.fn_inc_search(
|
||||
p_ticket_id text DEFAULT NULL,
|
||||
p_owner text DEFAULT NULL,
|
||||
p_cluster text DEFAULT NULL,
|
||||
p_status text DEFAULT NULL,
|
||||
p_state text DEFAULT 'closed',
|
||||
p_from timestamptz DEFAULT NULL,
|
||||
p_to timestamptz DEFAULT NULL,
|
||||
p_limit integer DEFAULT 500
|
||||
)
|
||||
RETURNS jsonb LANGUAGE plpgsql STABLE AS $fn$
|
||||
DECLARE
|
||||
v_state text := lower(COALESCE(NULLIF(p_state, ''), 'closed'));
|
||||
v_limit integer := LEAST(GREATEST(COALESCE(p_limit, 500), 1), 5000);
|
||||
v_result jsonb;
|
||||
BEGIN
|
||||
p_ticket_id := NULLIF(trim(p_ticket_id), '');
|
||||
p_owner := NULLIF(trim(p_owner), '');
|
||||
p_cluster := NULLIF(p_cluster, '');
|
||||
p_status := NULLIF(p_status, '');
|
||||
|
||||
WITH hits AS (
|
||||
SELECT ticket_id, normalized_status, cluster, region, location_name,
|
||||
initcap(lower(NULLIF(owner, ''))) AS owner, assigned_team,
|
||||
sla_status, mttr, closed_at, created_at_service, is_actionable,
|
||||
CASE WHEN geom IS NOT NULL THEN ST_Y(geom) END AS lat,
|
||||
CASE WHEN geom IS NOT NULL THEN ST_X(geom) END AS lng
|
||||
FROM tickets.inc
|
||||
WHERE (p_ticket_id IS NULL OR ticket_id ILIKE '%' || p_ticket_id || '%')
|
||||
AND (p_owner IS NULL OR lower(owner) LIKE '%' || lower(p_owner) || '%')
|
||||
AND (p_cluster IS NULL OR cluster = p_cluster)
|
||||
AND (p_status IS NULL OR normalized_status = p_status)
|
||||
AND CASE v_state
|
||||
WHEN 'open' THEN COALESCE(is_actionable, false)
|
||||
WHEN 'all' THEN COALESCE(is_actionable, false)
|
||||
OR (closed_at IS NOT NULL
|
||||
AND (p_from IS NULL OR closed_at >= p_from)
|
||||
AND (p_to IS NULL OR closed_at < p_to))
|
||||
ELSE NOT COALESCE(is_actionable, false) -- 'closed'
|
||||
AND closed_at IS NOT NULL
|
||||
AND (p_from IS NULL OR closed_at >= p_from)
|
||||
AND (p_to IS NULL OR closed_at < p_to)
|
||||
END
|
||||
),
|
||||
total AS (SELECT count(*) AS n FROM hits),
|
||||
page AS (
|
||||
SELECT * FROM hits
|
||||
ORDER BY closed_at DESC NULLS LAST, created_at_service DESC NULLS LAST
|
||||
LIMIT v_limit
|
||||
)
|
||||
SELECT jsonb_build_object(
|
||||
'count', (SELECT n FROM total),
|
||||
'truncated', (SELECT n FROM total) > v_limit,
|
||||
'limit', v_limit,
|
||||
'state', v_state,
|
||||
'rows', COALESCE((SELECT jsonb_agg(to_jsonb(page)
|
||||
ORDER BY page.closed_at DESC NULLS LAST,
|
||||
page.created_at_service DESC NULLS LAST)
|
||||
FROM page), '[]'::jsonb)
|
||||
) INTO v_result;
|
||||
|
||||
RETURN v_result;
|
||||
END $fn$;
|
||||
|
||||
COMMENT ON FUNCTION reporting.fn_inc_search(text, text, text, text, text, timestamptz, timestamptz, integer) IS
|
||||
'FleetOps ticket explorer: search tickets.inc by id/owner/cluster/status/state/time '
|
||||
'(owner case-normalized). Returns {count, truncated, rows}. fleettickets 13.';
|
||||
|
||||
-- grants (guarded: roles may not exist on a fresh DB)
|
||||
DO $grants$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dashboard_ro') THEN
|
||||
GRANT EXECUTE ON FUNCTION reporting.fn_inc_search(text, text, text, text, text, timestamptz, timestamptz, integer) TO dashboard_ro;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
|
||||
GRANT EXECUTE ON FUNCTION reporting.fn_inc_search(text, text, text, text, text, timestamptz, timestamptz, integer) TO grafana_ro;
|
||||
END IF;
|
||||
END $grants$;
|
||||
39
migrations/14_inc_filter_options.sql
Normal file
39
migrations/14_inc_filter_options.sql
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
-- 14_inc_filter_options.sql — fleettickets · ticket-explorer dropdown options
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- reporting.fn_inc_filter_options feeds the FleetOps ticket-explorer pulldowns:
|
||||
-- distinct engineers (owner, case-normalized like migration 12/13), distinct
|
||||
-- clusters, and the currently-open ticket ids (closed ids are ~22k → too many for a
|
||||
-- pulldown; use the explorer's ticket-id substring search for historical lookups).
|
||||
-- Backs GET /webhook/inc-filter-options. Idempotent (CREATE OR REPLACE).
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
SET search_path = tickets, public;
|
||||
|
||||
CREATE OR REPLACE FUNCTION reporting.fn_inc_filter_options()
|
||||
RETURNS jsonb LANGUAGE sql STABLE AS $fn$
|
||||
SELECT jsonb_build_object(
|
||||
'owners', (SELECT COALESCE(jsonb_agg(o ORDER BY o), '[]'::jsonb)
|
||||
FROM (SELECT DISTINCT initcap(lower(NULLIF(owner, ''))) AS o
|
||||
FROM tickets.inc WHERE NULLIF(owner, '') IS NOT NULL) s),
|
||||
'clusters', (SELECT COALESCE(jsonb_agg(c ORDER BY c), '[]'::jsonb)
|
||||
FROM (SELECT DISTINCT cluster AS c
|
||||
FROM tickets.inc WHERE NULLIF(cluster, '') IS NOT NULL) s),
|
||||
'open_ticket_ids', (SELECT COALESCE(jsonb_agg(ticket_id ORDER BY ticket_id), '[]'::jsonb)
|
||||
FROM tickets.inc WHERE COALESCE(is_actionable, false))
|
||||
);
|
||||
$fn$;
|
||||
|
||||
COMMENT ON FUNCTION reporting.fn_inc_filter_options() IS
|
||||
'FleetOps ticket explorer dropdown options: distinct owners (normalized), clusters, '
|
||||
'and open ticket ids. fleettickets 14.';
|
||||
|
||||
-- grants (guarded: roles may not exist on a fresh DB)
|
||||
DO $grants$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dashboard_ro') THEN
|
||||
GRANT EXECUTE ON FUNCTION reporting.fn_inc_filter_options() TO dashboard_ro;
|
||||
END IF;
|
||||
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
|
||||
GRANT EXECUTE ON FUNCTION reporting.fn_inc_filter_options() TO grafana_ro;
|
||||
END IF;
|
||||
END $grants$;
|
||||
Loading…
Reference in a new issue