[APP-367] feat: Update subset_sql to use basic filters from request#3160
[APP-367] feat: Update subset_sql to use basic filters from request#3160emyl3 wants to merge 43 commits into
Conversation
# Conflicts: # apps/modernization-api/src/main/java/gov/cdc/nbs/report/ReportService.java
| public class FieldFormatter { | ||
| public String formatField(String type, String value) { | ||
| return switch (type.toUpperCase()) { | ||
| case "STRING" -> "'" + value.replace("'", "''") + "'"; |
There was a problem hiding this comment.
do we need to do any other sql escaping?
|
|
||
| for (FilterConfiguration filter : reportConfig.filters()) { | ||
| findColumn(reportConfig, filter) | ||
| .ifPresent( |
There was a problem hiding this comment.
what about building filters that don't have columns? do all basic filters target a column?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); | ||
| }); |
There was a problem hiding this comment.
(q, b): should there be an error if there's no column?
There was a problem hiding this comment.
yup, I'll change to throw an exception
mcmcgrath13
left a comment
There was a problem hiding this comment.
Overall structure looks good, but need to fix up the data source for the filter building
# 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"); |
There was a problem hiding this comment.
| 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)) |
There was a problem hiding this comment.
can we also test too long?
| String whereFragment = | ||
| mockWhereClauseService.buildBasicWhereFragment(reportConfig, basicFilterRequest); | ||
|
|
||
| assertThat(whereFragment).isEqualTo("([ColumnName] IN (condition1))"); |
There was a problem hiding this comment.
| assertThat(whereFragment).isEqualTo("([ColumnName] IN (condition1))"); | |
| assertThat(whereFragment).isEqualTo("([ColumnName] IN ('condition1'))"); |
| mockWhereClauseService = new WhereClauseService(fieldFormatter); | ||
| // Default behavior for formatter: return value as is | ||
| Mockito.lenient() | ||
| .when(fieldFormatter.formatField(anyString(), anyString())) | ||
| .thenAnswer(ff -> ff.getArgument(1)); |
There was a problem hiding this comment.
(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?
There was a problem hiding this comment.
same with the field formatter - seems like those both could be static classes
| import java.util.List; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component |
There was a problem hiding this comment.
(q, nb): why have this be a component instead of just a regular utility class?
| filterType?: string; | ||
| filterName?: string; | ||
| codeSetName?: string; | ||
| type?: string; |
There was a problem hiding this comment.
(comm, b): The rest of the UI needs to be updated to use these new names, same with defaultValues
There was a problem hiding this comment.
I think #3217 should address why the tests are still passing even though there're a lot of off references
There was a problem hiding this comment.
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) {} |
There was a problem hiding this comment.
(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() : ""; |
There was a problem hiding this comment.
(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?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
(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); |
There was a problem hiding this comment.
(sugg, b): can we add the defaultIncludeNulls to the assertion block?
There was a problem hiding this comment.
(and have a test where it will be false)
There was a problem hiding this comment.
added for true and false
| @BeforeEach | ||
| void setUp() { | ||
| // Instantiate real dependencies | ||
| FieldFormatter fieldFormatter = new FieldFormatter(); |
| Long reportColumnUid, | ||
| Boolean defaultIncludeNulls, | ||
| String type) { | ||
| BasicFilterConfiguration basicFilterConfiguration = |
There was a problem hiding this comment.
(q, nb): why mock here vs use the real filter configuration? it's a relatively straightforward object
There was a problem hiding this comment.
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)"); |
There was a problem hiding this comment.
(sugg, b): could we add a test that combines this with the AND-ing to ensure overall precedence/parens end up correct?
|



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()inReportSQLBuilder. This only implements the basic filters.Tickets
Checklist before requesting a review