refactor: consolidate duplicated code to reduce LOC (~570 net)#431
Conversation
Behavior-preserving consolidation across 33 modules, removing duplicate functions, single-use wrappers, and hand-written instances in favour of shared helpers, library primitives, and deriving. Net ~ -570 LOC. Highlights: - OTLP: unify processTraceRequest/processLogsRequest via processSignalRequest; collapse the three *ServiceRpcHandler into one mkOtlpRpcHandler; reuse getValidTimestamp; generalize the span/log attribute extractors. - AI: merge runAgenticLoop into runAgenticLoopRaw; collapse runKqlAndFormat into runKqlWithRawData; share the LLM judge-prompt builder. - Telemetry: parameterize the otel-span lookup queries (selectOtelSpans); derive AggregationTemporality via WrappedEnumInt; reuse atMapText/atMapInt. - KQL parser: single shared binary-operator table drives pTerm, Display and ToQueryText (decode keyed by parse-symbol, not list position). - Email/bots: shared email footer + billingNotifEmail skeleton; shared report header; merged dashboard Block Kit builder. - Misc: merge monitor*ByIds timestamp helpers, getDashboardsFor* queries, LemonSqueezy fetch helpers; inline single-use shims (mergeJsonObjects, eitherStrToText, lookupVec*); dedupe ISO-8601 formatter and Slack channel fetch; tidy MCP/CLI/Web handler envelopes; remove dead CLI/Utils helpers. atMapInt uses toBoundedInteger (out-of-range/non-integral -> Nothing) to preserve prior status-code semantics. runAgenticQuery parses the final text only (no tool_calls injection) so MCP/bot/log-explorer responses are unchanged. Verified: library compiles (119 modules), doctests 668/0, integration suite green (Agentic golden specs 11/0); pre-existing GitSync Real-API e2e tests require GH_TEST_PAT and are unaffected.
Code ReviewGreat cleanup — net -570 LOC across 33 modules is meaningful, and the consolidations are mostly well-chosen. The OTLP unification, KQL operator table, and golden-cache helper are all solid abstractions. A few things worth addressing before merge: Correctness / Bugs1. The old 2. Unused After inlining 3. The Potential Issues4. Pre-existing, not introduced here, but the refactor was a natural opportunity to fix it: the function calls 5.
6. The original used Style / Minor7. 8. 9. What's working well
Overall: items 1–3 should be addressed before merge. Items 4–6 are worth a quick check. The rest are minor. |
- atMapInt: restore round (toBoundedInteger dropped fractional/oversized numbers) - scrubNulText: restore NUL deletion (U+FFFD changed stored content vs history) - Anomalies: restore parseStoredJSON helper instead of inlined duplicate - hlint: avoid-lambda (LLM, Wreq), eta-reduce billing email helpers
Code Review - PR #431: consolidation/LOC reductionOverall this is well-executed. The structural patterns (withGoldenCache, WrappedEnumInt, processSignalRequest, mkOtlpRpcHandler, binary-op tables, buildJudgePrompt, selectOtelSpans, BulkOpResult) are all solid uses of the codebase's existing machinery. A few issues to address before merging: Bugs / Behaviour Changes1. scrubNulText now strips NUL instead of replacing with U+FFFD (src/Utils.hs) The old behaviour preserved string length (substituted visible replacement char U+FFFD); the new one removes the byte entirely. Callers that previously showed a replacement glyph for NUL in trace attributes get a shorter string. The docstring was updated to say "Strip" - confirm this is intentional rather than a copy-paste from the local cleanNullBytes helper that used filter. 2. Display Expr / ToQueryText Expr lose compile-time exhaustiveness (src/Pkg/Parser/Expr.hs) The previous hand-enumerated pattern matches gave a GHC exhaustiveness warning if a new Expr constructor was added without updating Display/ToQueryText. The new fall-through with 3. dashboardSelectBlock adds "Untitled" guard to widget names (src/Pages/Bots/Slack.hs) The merged builder applies 4. getRequestDetails uses Map.member instead of pattern-matching on AE.Object (src/Pages/LogExplorer/LogItem.hs) Old code only entered the HTTP branch when 5. FromField (Map Text AE.Value) - verify AesonText covers the varchar fallback (src/Models/Telemetry/Telemetry.hs) Old code tried JSONB first, then a text/varchar parse as fallback (tryJsonb <|> tryText). New code delegates entirely to AesonText's FromField via coerce. If AesonText's fromField only handles JSONB and not varchar/text, the fallback path is silently dropped. Worth confirming DeriveUtils.AesonText handles both. Minor style notesgetRequestDetails triple-nested ifs - less readable than the prior case chain. asum with guard or <|> would be more idiomatic and remove a nesting level. processSignalRequest positional Text args - 4 consecutive Text parameters (label, signal, receivedMsg, noun) are easy to transpose; a small record would make future edits safer. Not a blocker. unAcknowlegeGet route binding (src/Web/Routes.hs) - grew a lambda because the merged handler added What is working well
Summary: Items 2 (exhaustiveness loss in Expr instances) and 5 (AesonText varchar fallback) are the highest-priority checks. Items 1, 3, and 4 are genuine behaviour changes that should be deliberate. Everything else is optional polish. |
Code Review — refactor: consolidate duplicated code to reduce LOCOverall this is a well-structured, well-documented refactor. The LOC reduction is real, the split into three phases was the right call, and the test/doctest coverage is solid. A few issues worth addressing before merge: Issues1. -- Old: replaces \NUL with U+FFFD (replacement char)
scrubNulText t = if T.any (== '\NUL') t then T.replace "\NUL" "\xFFFD" t else t
-- New: strips \NUL entirely
scrubNulText t = if T.any (== '\NUL') t then T.filter (/= '\NUL') t else tThe PR description claims "behavior-preserving consolidation" but this is a semantic change: the old path turned 2. -- New
displayPrec _ _ = error "Display Expr: unreachable"
toQText _ = error "ToQueryText Expr: unreachable"The old exhaustive match gave a compile-time guarantee that every 3. instance (Bounded a, Enum a, Typeable a) => FromField (WrappedEnumInt a) where
fromField f bs = fromField @Int f bs
>>= \n -> maybe (returnError ConversionFailed f ...) (pure . WrappedEnumInt) (safeToEnum n)
instance (Bounded a, Enum a) => HI.DecodeValue (WrappedEnumInt a) where
decodeValue = WrappedEnumInt . fromMaybe minBound . safeToEnum <$> HI.decodeValue
4. subBinaryParts e = do
(s, v, sym) <- case e of
Eq s v -> at s v "=="
...
(_, _, qtok, dop) <- find (\(_, sym', _, _) -> sym' == sym) subjectBinOps
pure (s, v, dop, qtok)This does two passes: match constructor → symbol string, then look up that symbol in the table. A typo in any symbol string compiles cleanly but fails silently (returns Minor Notes5. processSignalRequest label signal receivedMsg noun countKey metadataApiKey projectKeys atIds' convertEight positional arguments of the same type are easy to mix up at call sites. A small 6. deriving stock instance Generic LLMCore.Message
deriving anyclass instance AE.ToJSON LLMCore.Message
deriving anyclass instance AE.FromJSON LLMCore.MessageThe PR notes byte-identical semantics — this holds only if 7. (.formatted) <$> runKqlWithRawData config kql [] (\(results,_,_) -> (formattedText, AE.Null))Four callers do this. The What's good
|
Summary
Behavior-preserving consolidation across 33 modules: removes duplicate functions, single-use wrappers, and hand-written instances in favour of shared helpers, library primitives, and deriving. Net **~ -570 LOC** (+899 / -1469 on a +906-LOC-opportunity audit; the rest were skipped as cross-module/behavior-changing).
This was driven by a codebase-wide audit (79 candidate opportunities, adversarially verified) and applied in three phases: low-risk mechanical → duplicate-function/SQL/render merges → structural (OTLP request unification, golden-cache helper).
Highlights
OtlpServer.hs): unifyprocessTraceRequest/processLogsRequestviaprocessSignalRequest; collapse the three*ServiceRpcHandlerinto onemkOtlpRpcHandler; reusegetValidTimestamp; generalize the span/log attribute extractors.Pkg/AI.hs): mergerunAgenticLoopintorunAgenticLoopRaw; collapserunKqlAndFormatintorunKqlWithRawData; share the LLM judge-prompt builder (PatternMerge.hs).selectOtelSpans); deriveAggregationTemporalityviaWrappedEnumInt; reuseatMapText/atMapInt.Parser/Expr.hs): one shared binary-operator table drivespTerm,Display, andToQueryText— decode is keyed by parse-symbol (not list position) so reordering for parser precedence stays safe.billingNotifEmailskeleton; shared report header; merged dashboard Block Kit builder.monitor*ByIdstimestamp helpers,getDashboardsFor*queries, LemonSqueezy fetch helpers; inline single-use shims (mergeJsonObjects,eitherStrToText,lookupVec*); dedupe the ISO-8601 formatter and Slack channel fetch; tidy MCP/CLI/Web handler envelopes; remove dead CLI/Utils helpers.Correctness notes
atMapIntusestoBoundedInteger(out-of-range/non-integral →Nothing) to preserve prior status-code semantics rather thanround.runAgenticQueryparses the final text only (notool_callsinjection), so MCP / bot / log-explorer responses are unchanged; the tool-data-reuse path stays onrunAgenticChatWithHistory→AgenticChatResult(used by Anomalies).Out of scope (deliberately)
Html ().Verification
Pages.Bots.Agenticgolden specs (which caught an earlytool_callsregression, since fixed) pass 11/0.Pages.GitSync"Real API" e2e tests requireGH_TEST_PAT/OWNER/REPOand are unaffected by this change.