Skip to content

[APP-367] feat: Update subset_sql to use basic filters from request#3160

Open
emyl3 wants to merge 43 commits into
mainfrom
el/app-367
Open

[APP-367] feat: Update subset_sql to use basic filters from request#3160
emyl3 wants to merge 43 commits into
mainfrom
el/app-367

Conversation

@emyl3
Copy link
Copy Markdown
Contributor

@emyl3 emyl3 commented Apr 13, 2026

Description

The PR takes basic filters from a ReportConfiguration then creates a WHERE clause and adds that to the subset_sql. The funcationality has been pulled from buildWhereClause() in ReportSQLBuilder. This only implements the basic filters.

Tickets

Checklist before requesting a review

  • PR focuses on a single story
  • Code has been fully tested to meet acceptance criteria
  • PR is reasonably small and reviewable (Generally less than 10 files and 500 changed lines)
  • All new functions/classes/components reasonably small
  • Functions/classes/components focused on one responsibility
  • Code easy to understand and modify (clarity over concise/clever)
  • PRs containing TypeScript follow the Do's and Don'ts
  • PR does not contain hardcoded values (Uses constants)
  • All code is covered by unit or feature tests

@brick-green brick-green changed the title VERY WIP Handoff - [APP-367] feat: Update subset_sql to use basic filters from request APP-367] feat: Update subset_sql to use basic filters from request Apr 22, 2026
@brick-green brick-green changed the title APP-367] feat: Update subset_sql to use basic filters from request [APP-367] feat: Update subset_sql to use basic filters from request Apr 22, 2026
@brick-green brick-green marked this pull request as ready for review April 22, 2026 15:15
@brick-green brick-green requested a review from a team as a code owner April 22, 2026 15:15
@brick-green brick-green requested review from JordanGuinn and mcmcgrath13 and removed request for a team April 22, 2026 15:15
public class FieldFormatter {
public String formatField(String type, String value) {
return switch (type.toUpperCase()) {
case "STRING" -> "'" + value.replace("'", "''") + "'";
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.

do we need to do any other sql escaping?


for (FilterConfiguration filter : reportConfig.filters()) {
findColumn(reportConfig, filter)
.ifPresent(
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.

what about building filters that don't have columns? do all basic filters target a column?

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.

The current NBS6 code callsgetDataSourceColumnDT() as one of the first steps. That method throws an exception if a column is not found. I was just working from that. Is that a safe route?

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.

I think so. The advanced filter doesn't have a column, but I'm pretty sure every basic filter does and since this is for basic an error makes sense

column -> {
String segment = buildFilterFragment(filter, column);
if (!segment.isEmpty()) finalWhere.add(segment);
});
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.

(q, b): should there be an error if there's no column?

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.

yup, I'll change to throw an exception

Comment thread apps/modernization-api/src/main/java/gov/cdc/nbs/report/WhereClauseService.java Outdated
Comment thread apps/modernization-api/src/main/java/gov/cdc/nbs/report/WhereClauseService.java Outdated
Copy link
Copy Markdown
Contributor

@mcmcgrath13 mcmcgrath13 left a comment

Choose a reason for hiding this comment

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

Overall structure looks good, but need to fix up the data source for the filter building

@brick-green brick-green marked this pull request as draft April 23, 2026 16:43
# Conflicts:
#	apps/modernization-api/src/main/java/gov/cdc/nbs/report/ReportService.java
#	apps/modernization-api/src/main/java/gov/cdc/nbs/report/models/AdvancedFilterConfiguration.java
#	apps/modernization-ui/src/generated/models/BasicFilterConfiguration.ts
assertThat(monthResult.get(1)).isEqualTo("'2024-02-29'"); // Leap year check

// Test yyyy Range
List<String> yearRange = List.of("2023", "2023");
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.

Suggested change
List<String> yearRange = List.of("2023", "2023");
List<String> yearRange = List.of("2023", "2024");

to stress test leap years

@Test
void should_throw_exception_for_invalid_range_size() {
List<String> tooShort = List.of("2023");
assertThatThrownBy(() -> formatter.convertToSQLFromDateRange(tooShort))
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.

can we also test too long?

String whereFragment =
mockWhereClauseService.buildBasicWhereFragment(reportConfig, basicFilterRequest);

assertThat(whereFragment).isEqualTo("([ColumnName] IN (condition1))");
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.

Suggested change
assertThat(whereFragment).isEqualTo("([ColumnName] IN (condition1))");
assertThat(whereFragment).isEqualTo("([ColumnName] IN ('condition1'))");

Comment on lines +36 to +40
mockWhereClauseService = new WhereClauseService(fieldFormatter);
// Default behavior for formatter: return value as is
Mockito.lenient()
.when(fieldFormatter.formatField(anyString(), anyString()))
.thenAnswer(ff -> ff.getArgument(1));
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.

(q, nb): why do we need to mock the where clause service? it's stateless so couldn't we just use it? My gut-code-smell still doesn't really grok why the where clause service is a service and not just a class that's normally imported and used where it needs using (and then we wouldn't mock it, but just use it) - can you eli5?

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.

same with the field formatter - seems like those both could be static classes

import java.util.List;
import org.springframework.stereotype.Component;

@Component
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.

(q, nb): why have this be a component instead of just a regular utility class?

filterType?: string;
filterName?: string;
codeSetName?: string;
type?: string;
Copy link
Copy Markdown
Contributor

@mcmcgrath13 mcmcgrath13 May 11, 2026

Choose a reason for hiding this comment

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

(comm, b): The rest of the UI needs to be updated to use these new names, same with defaultValues

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.

I think #3217 should address why the tests are still passing even though there're a lot of off references

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.

think I got all these fixed

@Schema(requiredMode = Schema.RequiredMode.REQUIRED) Long reportFilterUid,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) List<String> values) {}
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) List<String> values,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) Boolean includeNulls) {}
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.

(thought, nb): as we get around to the front end side of this work, we might want to reconsider the required-ness here since not all filters allow nulls, so it may be simpler to allow missingness as false

finalWhere.add(basicWhereFragment);

// Only return the WHERE clause if it contains anything beyond the initial "WHERE " prefix
return finalWhere.length() > 6 ? finalWhere.toString() : "";
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.

(nit, nb): might be minor-ly more readable to pull out WHERE to a constant and then change this to finalWhere.equals(SQL_WHERE) and flip the ternary branches so then it's a more explicit check vs implicit with the comment. Though maybe that doesn't work easily with string joiners?

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.

I like this. I already used a const for the AND so another for WHERE feels right. Added

* @param column The metadata for the column being filtered.
* @return A parenthesized SQL fragment.
*/
private String buildBasicTimeRangeCriteria(
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.

(sugg, nb): it seems like the logic here is almost the same as the standard one - would it make sense to combine and instead branch at the point of formatting the primary clause?

assertThat(mapped.reportColumnUid()).isEqualTo(column.getId());
assertThat(mapped.defaultValue()).isEqualTo(List.of("value"));
assertThat(mapped.defaultValues()).isEqualTo(List.of("value"));
assertThat(mapped.minValueCount()).isEqualTo(1);
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.

(sugg, b): can we add the defaultIncludeNulls to the assertion block?

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.

(and have a test where it will be false)

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.

added for true and false

@BeforeEach
void setUp() {
// Instantiate real dependencies
FieldFormatter fieldFormatter = new FieldFormatter();
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.

❤️

Long reportColumnUid,
Boolean defaultIncludeNulls,
String type) {
BasicFilterConfiguration basicFilterConfiguration =
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.

(q, nb): why mock here vs use the real filter configuration? it's a relatively straightforward object

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.

got a little mock happy on the work. Switched to instantiated config

String whereFragment =
mockWhereClauseService.buildBasicWhereFragment(reportConfig, basicFilterRequest);

assertThat(whereFragment).isEqualTo("([ColumnName] IN ('condition1') OR [ColumnName] IS NULL)");
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.

(sugg, b): could we add a test that combines this with the AND-ing to ensure overall precedence/parens end up correct?

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.

good call - added

@sonarqubecloud
Copy link
Copy Markdown

@brick-green brick-green requested a review from mcmcgrath13 May 12, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants