Skip to content

[OPIK-5023] [BE] refactor(filter): introduce ENUM_LEGACY field type for source legacy fallback#5846

Merged
thiagohora merged 10 commits intomainfrom
thiaghora/OPIK-5023-filter-enum-legacy-refactor
Mar 27, 2026
Merged

[OPIK-5023] [BE] refactor(filter): introduce ENUM_LEGACY field type for source legacy fallback#5846
thiagohora merged 10 commits intomainfrom
thiaghora/OPIK-5023-filter-enum-legacy-refactor

Conversation

@thiagohora
Copy link
Copy Markdown
Contributor

Details

Centralises the legacy-fallback operator handling for the source field inside ANALYTICS_DB_OPERATOR_MAP, consistent with how every other operator/type pair works.

Before this change, buildWithLegacyFallback held a switch (operator) that duplicated the EQUAL/NOT_EQUAL SQL already encoded in the map. The fix:

  • Introduces FieldType.ENUM_LEGACY with its own EQUAL and NOT_EQUAL templates that include the OR/AND fallback clause (%3$s = legacy DB value)
  • TraceField.SOURCE and SpanField.SOURCE re-typed to ENUM_LEGACY; legacy behaviour is now driven by the field type, not a special-case method
  • Removes LEGACY_FALLBACK_OPERATOR_MAP and buildWithLegacyFallback
  • FiltersFactory validation map updated with the new type

Change checklist

  • User facing
  • Documentation update

Issues

  • OPIK-5023

AI-WATERMARK

AI-WATERMARK: yes

  • Tools: Claude Code
  • Model(s): claude-sonnet-4-6
  • Scope: assisted (filter refactor and review comment resolution)
  • Human verification: code review + compilation check

Testing

  • mvn compile -DskipTests — clean
  • mvn spotless:apply — no formatting changes needed
  • Existing integration tests in SpansResourceTest and TracesResourceTest cover source filtering (EQUAL / NOT_EQUAL) including the legacy fallback path

Documentation

N/A

…or source legacy fallback

Centralise the legacy-fallback operator handling inside ANALYTICS_DB_OPERATOR_MAP
by introducing FieldType.ENUM_LEGACY. The EQUAL and NOT_EQUAL templates for the
new type encode the OR/AND fallback SQL directly in the map, removing the need for
the separate LEGACY_FALLBACK_OPERATOR_MAP and buildWithLegacyFallback method.

TraceField.SOURCE and SpanField.SOURCE are re-typed to ENUM_LEGACY so the behaviour
is driven by the field type, consistent with every other operator/type pair.
@github-actions github-actions bot added java Pull requests that update Java code Backend labels Mar 25, 2026
@github-actions github-actions bot added the tests Including test files, or tests related like configuration. label Mar 25, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

Backend Tests - Integration Group 8

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit a32b412.

♻️ This comment has been updated with latest results.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 25, 2026

Backend Tests - Integration Group 3

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit a32b412.

♻️ This comment has been updated with latest results.

…tils

Replace duplicate getValidValue/getKey/getInvalidValue implementations
in FindTraceThreadsResourceTest and GetTracesByProjectResourceTest with
delegation to the shared FilterTestUtils utility class.
…ackDbValue

Fix MissingFormatArgumentException in toAnalyticsDbFilter when an
ENUM_LEGACY field has no legacy fallback for a given value (e.g.
source = "ui"): fall back to the plain ENUM template for that operator
instead of calling template.formatted with too few arguments.

Replace identical legacyFallbackDbValue anonymous overrides on
TraceField.SOURCE and SpanField.SOURCE with a constructor-injected
Function, eliminating the duplicate enum constant body.
@thiagohora thiagohora marked this pull request as ready for review March 25, 2026 14:12
@thiagohora thiagohora requested a review from a team as a code owner March 25, 2026 14:12
@thiagohora thiagohora requested a review from andrescrz March 25, 2026 14:13
andrescrz
andrescrz previously approved these changes Mar 27, 2026
Copy link
Copy Markdown
Member

@andrescrz andrescrz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just a few optional comments to improve the design in the future.


private final String queryParamField;
private final FieldType type;
private final Function<String, Optional<String>> legacyFallback;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this new function is more tied to the types (ENUM_SOURCE having an actual implementation, with the rest having the default) more than to the specific fields. This way, you don't need to DRY for all fields using this new type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit ac1fa1a addressed this comment by moving the legacy fallback handling into the FieldType enum so that ENUM_LEGACY overrides buildFilter/legacyFallback instead of each field carrying its own Function. SpanField, TraceField, and TraceThreadField now simply reference FieldType.ENUM_LEGACY (with the fallback logic centralized), eliminating the need to duplicate the legacy handling per field.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac1fa1a — moved the fallback logic to FieldType.ENUM_LEGACY via a buildFilter override. ENUM_LEGACY now calls Source.legacyFallbackDbValue directly and handles the OR/AND clause itself. SpanField.SOURCE, TraceField.SOURCE, and TraceThreadField.SOURCE simply declare FieldType.ENUM_LEGACY — no lambda or extra constructor needed. Adding another field with the same type automatically inherits the behaviour.

🤖 Reply posted via /address-github-pr-comments

Comment on lines +931 to +932
if (filter.field().getType() == FieldType.ENUM_LEGACY) {
return filter.field().legacyFallbackDbValue(filter.value())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my other comment, it'd be great if we didn't need to discriminate per type here, so we don't need to maintain as we add new types with related functionality.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit ac1fa1a addressed this comment by replacing the explicit FieldType check with a call into FieldType.buildFilter, letting each type handle its own formatting. The new ENUM_LEGACY override encapsulates the legacy fallback logic, so toAnalyticsDbFilter no longer needs discriminating branches.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac1fa1a — the if (getType() == ENUM_LEGACY) block is gone. toAnalyticsDbFilter now calls filter.field().getType().buildFilter(...) unconditionally; each FieldType constant decides how to format the clause. Only ENUM_LEGACY overrides buildFilter; all other types use the default 2-arg implementation. Adding a new type with special formatting only requires overriding buildFilter in FieldType — the builder stays untouched.

🤖 Reply posted via /address-github-pr-comments

return filter.field().legacyFallbackDbValue(filter.value())
.map(fallback -> "(%s)".formatted(template.formatted(dbField, i, fallback)))
.orElseGet(() -> "(%s)".formatted(
ANALYTICS_DB_OPERATOR_MAP.get(filter.operator()).get(FieldType.ENUM).formatted(dbField,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: is there a possibility of not having fallback here in spite of having the outer check on ENUM_LEGACY above?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — Source.legacyFallbackDbValue only returns a value for sdk (maps to the legacy unknown DB value). For every other source (e.g. ui, online_scoring) it returns Optional.empty(), so the orElseGet path is reachable for any valid non-sdk filter. In that case the plain ENUM template is used, which correctly filters only on the new DB column value. This path now lives inside FieldType.ENUM_LEGACY.buildFilter (ac1fa1a) where the context is self-contained.

🤖 Reply posted via /address-github-pr-comments

Comment on lines +9 to +11
public class FilterTestUtils {

private FilterTestUtils() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: lombok for utils classes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commit ac1fa1a addressed this comment by adding Lombok’s @UtilityClass annotation and removing the manual private constructor, conferring the desired utility-class behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac1fa1a — replaced the manual private constructor with @UtilityClass.

🤖 Reply posted via /address-github-pr-comments

…dType

Per review feedback:
- ENUM_LEGACY.buildFilter overrides the FieldType default, encoding the
  OR/AND legacy-clause logic directly on the type. Source::legacyFallbackDbValue
  is called once, in one place.
- Field enums (SpanField, TraceField, TraceThreadField) no longer carry a
  Function<String,Optional<String>> lambda; SOURCE just uses ENUM_LEGACY.
- FilterQueryBuilder.toAnalyticsDbFilter delegates to the type's buildFilter,
  removing the if(ENUM_LEGACY) discriminator. Adding a new type with similar
  behaviour only requires overriding buildFilter in FieldType.
- legacyFallbackDbValue removed from Field interface; it lives on FieldType.
- FilterTestUtils uses @UtilityClass instead of manual private constructor.
@thiagohora thiagohora requested a review from andrescrz March 27, 2026 13:02
Copy link
Copy Markdown
Contributor

@BorisTkachenko BorisTkachenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@thiagohora thiagohora merged commit 34806ac into main Mar 27, 2026
76 checks passed
@thiagohora thiagohora deleted the thiaghora/OPIK-5023-filter-enum-legacy-refactor branch March 27, 2026 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend java Pull requests that update Java code tests Including test files, or tests related like configuration.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants