From 6ed81fb14dd6e7b7d1575ad75b20f20ac879c8ad Mon Sep 17 00:00:00 2001 From: Eric Buckley Date: Fri, 6 Mar 2026 14:07:09 -0800 Subject: [PATCH 1/3] ci(build): add Spotless and Google Java Format Integrate the Spotless Gradle plugin to enforce consistent Java code style using Google Java Format across all subprojects. Key changes: - Configure Spotless for Java with Google Java Format and cleanup rules. - Implement incremental formatting using 'ratchetFrom' to target only new changes by default. - Add a GitHub Action workflow to verify formatting on pull requests. - Update documentation with instructions for local usage and targeted formatting of specific files. --- .github/workflows/verify-formatting.yaml | 32 +++++++++++++++++++ README.md | 39 ++++++++++++++++++++++++ build.gradle | 27 ++++++++++++++-- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/verify-formatting.yaml diff --git a/.github/workflows/verify-formatting.yaml b/.github/workflows/verify-formatting.yaml new file mode 100644 index 000000000..5b4ddab80 --- /dev/null +++ b/.github/workflows/verify-formatting.yaml @@ -0,0 +1,32 @@ +name: Verify Formatting +on: + pull_request: + branches: + - main + +jobs: + spotless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: 21 + distribution: 'zulu' + + - name: Cache Gradle packages + uses: actions/cache@v5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Spotless Check + run: ./gradlew spotlessCheck diff --git a/README.md b/README.md index fc4739fbe..70c9a6285 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,45 @@ RTR uses Kafka to stream change events from transactional data sources into repo To set up a local development environment follow the guide [here](documentation/DevSetup.md) +## Code Quality and Formatting + +This project uses [Spotless](https://github.com/diffplug/spotless) with [Google Java Format](https://github.com/google/google-java-format) to maintain consistent code style. + +### Running Spotless Locally + +To check if your code adheres to the formatting standards: +```sh +./gradlew spotlessCheck +``` + +To automatically apply formatting fixes: +```sh +./gradlew spotlessApply +``` + +### Formatting Specific Files or Directories + +To run Spotless on a specific file or set of files, you can use the `-PspotlessFiles` property. Providing this property automatically bypasses the incremental (`ratchetFrom`) check, allowing you to force-format existing files. + +* **Format a specific service (bypasses the incremental check):** + ```sh + ./gradlew :liquibase-service:spotlessApply -PspotlessFiles='.*\.java' + ``` + +* **Format a specific file using its relative path:** + ```sh + ./gradlew spotlessApply -PspotlessFiles='liquibase-service/src/main/java/MyFile.java' + ``` + +* **Format multiple specific files (comma-separated):** + ```sh + ./gradlew spotlessApply -PspotlessFiles='File1.java,File2.java' + ``` + +> **Note:** If you use a subproject task (e.g., `:common-util:spotlessApply`), you can only target files within that subproject's directory. Use the root `spotlessApply` task if you want to target files across different services using their full repository paths. + +Formatting is enforced on all Pull Requests via GitHub Actions. It is recommended to run `spotlessApply` before committing your changes. + # CDCgov GitHub Organization Open Source Project Template **Template for clearance: This project serves as a template to aid projects in starting up and moving through clearance procedures. To start, create a new repository and implement the required [open practices](open_practices.md), train on and agree to adhere to the organization's [rules of behavior](rules_of_behavior.md), and [send a request through the create repo form](https://forms.office.com/Pages/ResponsePage.aspx?id=aQjnnNtg_USr6NJ2cHf8j44WSiOI6uNOvdWse4I-C2NUNk43NzMwODJTRzA4NFpCUk1RRU83RTFNVi4u) using language from this template as a Guide.** diff --git a/build.gradle b/build.gradle index 9bfef537e..d83d38a2a 100644 --- a/build.gradle +++ b/build.gradle @@ -13,13 +13,17 @@ buildscript { plugins { id 'org.sonarqube' version '4.2.1.3168' + id 'com.diffplug.spotless' version '8.3.0' } version = '1.0.1-SNAPSHOT' apply plugin: "com.dipien.semantic-version" -subprojects { +repositories { + mavenCentral() +} +subprojects { repositories { mavenCentral() maven { @@ -29,6 +33,25 @@ subprojects { apply plugin: 'java' apply plugin: 'jacoco' + apply plugin: 'com.diffplug.spotless' + + spotless { + // Disable incremental check if specific files are provided via CLI + if (!project.hasProperty('spotlessFiles')) { + ratchetFrom 'origin/main' + } + java { + target 'src/*/java/**/*.java' + googleJavaFormat() + formatAnnotations() + trimTrailingWhitespace() + endWithNewline() + } + } + + tasks.named('compileJava') { + dependsOn tasks.named('spotlessApply') + } jacocoTestReport { dependsOn test @@ -49,4 +72,4 @@ sonarqube { property "sonar.organization", "cdcgov" property "sonar.host.url", "https://sonarcloud.io" } -} \ No newline at end of file +} From f9289e1ff32049ba7a67ca0cbc67605b25cb1af0 Mon Sep 17 00:00:00 2001 From: Eric Buckley Date: Fri, 6 Mar 2026 14:08:42 -0800 Subject: [PATCH 2/3] style(liquibase-service): reformat code to 2-space indentation Reformatting just the liquibase-service java files to confirm the settings and workflow are working. --- .../EtlDataPipelineApplication.java | 6 +- .../controller/DataPipelineController.java | 20 +++---- .../service/DataPipelineStatusService.java | 13 ++--- .../EtlDataPipelineApplicationTests.java | 30 +++++----- .../DataPipelineControllerTest.java | 56 +++++++++---------- .../DataPipelineStatusServiceTest.java | 11 ++-- 6 files changed, 66 insertions(+), 70 deletions(-) diff --git a/liquibase-service/src/main/java/gov/cdc/etldatapipeline/EtlDataPipelineApplication.java b/liquibase-service/src/main/java/gov/cdc/etldatapipeline/EtlDataPipelineApplication.java index 5e217f8e5..b2ba13fa9 100644 --- a/liquibase-service/src/main/java/gov/cdc/etldatapipeline/EtlDataPipelineApplication.java +++ b/liquibase-service/src/main/java/gov/cdc/etldatapipeline/EtlDataPipelineApplication.java @@ -6,7 +6,7 @@ @SpringBootApplication public class EtlDataPipelineApplication { - public static void main(String[] args) { - SpringApplication.run(EtlDataPipelineApplication.class, args); - } + public static void main(String[] args) { + SpringApplication.run(EtlDataPipelineApplication.class, args); + } } diff --git a/liquibase-service/src/main/java/gov/cdc/etldatapipeline/controller/DataPipelineController.java b/liquibase-service/src/main/java/gov/cdc/etldatapipeline/controller/DataPipelineController.java index 76e10b600..3728c27d9 100644 --- a/liquibase-service/src/main/java/gov/cdc/etldatapipeline/controller/DataPipelineController.java +++ b/liquibase-service/src/main/java/gov/cdc/etldatapipeline/controller/DataPipelineController.java @@ -9,17 +9,15 @@ @RestController public class DataPipelineController { - DataPipelineStatusService dataPipelineStatusSvc; + DataPipelineStatusService dataPipelineStatusSvc; - public DataPipelineController(DataPipelineStatusService dataPipelineStatusSvc) { - this.dataPipelineStatusSvc = dataPipelineStatusSvc; - } - - - @GetMapping("/status") - @ResponseBody - public ResponseEntity getDataPipelineStatusHealth() { - return this.dataPipelineStatusSvc.getHealthStatus(); - } + public DataPipelineController(DataPipelineStatusService dataPipelineStatusSvc) { + this.dataPipelineStatusSvc = dataPipelineStatusSvc; + } + @GetMapping("/status") + @ResponseBody + public ResponseEntity getDataPipelineStatusHealth() { + return this.dataPipelineStatusSvc.getHealthStatus(); + } } diff --git a/liquibase-service/src/main/java/gov/cdc/etldatapipeline/service/DataPipelineStatusService.java b/liquibase-service/src/main/java/gov/cdc/etldatapipeline/service/DataPipelineStatusService.java index fe6658fe9..98c49da57 100644 --- a/liquibase-service/src/main/java/gov/cdc/etldatapipeline/service/DataPipelineStatusService.java +++ b/liquibase-service/src/main/java/gov/cdc/etldatapipeline/service/DataPipelineStatusService.java @@ -8,13 +8,12 @@ @Service public class DataPipelineStatusService { - private static final Logger LOG = LoggerFactory.getLogger(DataPipelineStatusService.class); + private static final Logger LOG = LoggerFactory.getLogger(DataPipelineStatusService.class); - public DataPipelineStatusService() { - } + public DataPipelineStatusService() {} - public ResponseEntity getHealthStatus(){ - LOG.info("Status OK"); - return ResponseEntity.status(HttpStatus.OK).body("Status OK"); - } + public ResponseEntity getHealthStatus() { + LOG.info("Status OK"); + return ResponseEntity.status(HttpStatus.OK).body("Status OK"); + } } diff --git a/liquibase-service/src/test/java/gov/cdc/etldatapipeline/EtlDataPipelineApplicationTests.java b/liquibase-service/src/test/java/gov/cdc/etldatapipeline/EtlDataPipelineApplicationTests.java index 3d53e906b..1dad7b092 100644 --- a/liquibase-service/src/test/java/gov/cdc/etldatapipeline/EtlDataPipelineApplicationTests.java +++ b/liquibase-service/src/test/java/gov/cdc/etldatapipeline/EtlDataPipelineApplicationTests.java @@ -1,25 +1,27 @@ package gov.cdc.etldatapipeline; +import static org.mockito.Mockito.mockStatic; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.boot.SpringApplication; -import static org.mockito.Mockito.mockStatic; - class EtlDataPipelineApplicationTests { - @Test - void testMainMethod() { - try (var springApplicationMock = mockStatic(SpringApplication.class)) { - // Act - EtlDataPipelineApplication.main(new String[]{}); + @Test + void testMainMethod() { + try (var springApplicationMock = mockStatic(SpringApplication.class)) { + // Act + EtlDataPipelineApplication.main(new String[] {}); - // Assert - springApplicationMock.verify(() -> SpringApplication.run(EtlDataPipelineApplication.class, new String[]{})); - } - } - @Test - void contextLoads() { - Assertions.assertTrue(true, "Unit Tests sanity check."); + // Assert + springApplicationMock.verify( + () -> SpringApplication.run(EtlDataPipelineApplication.class, new String[] {})); } + } + + @Test + void contextLoads() { + Assertions.assertTrue(true, "Unit Tests sanity check."); + } } diff --git a/liquibase-service/src/test/java/gov/cdc/etldatapipeline/controller/DataPipelineControllerTest.java b/liquibase-service/src/test/java/gov/cdc/etldatapipeline/controller/DataPipelineControllerTest.java index 2f4118e09..8245e457b 100644 --- a/liquibase-service/src/test/java/gov/cdc/etldatapipeline/controller/DataPipelineControllerTest.java +++ b/liquibase-service/src/test/java/gov/cdc/etldatapipeline/controller/DataPipelineControllerTest.java @@ -1,5 +1,9 @@ package gov.cdc.etldatapipeline.controller; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import gov.cdc.etldatapipeline.service.DataPipelineStatusService; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -10,41 +14,35 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - class DataPipelineControllerTest { - @Mock - private DataPipelineStatusService dataPipelineStatusService; + @Mock private DataPipelineStatusService dataPipelineStatusService; - @InjectMocks - private DataPipelineController controller; + @InjectMocks private DataPipelineController controller; - private AutoCloseable closeable; + private AutoCloseable closeable; - @BeforeEach - void setup() { - closeable = MockitoAnnotations.openMocks(this); - controller = new DataPipelineController(dataPipelineStatusService); - } + @BeforeEach + void setup() { + closeable = MockitoAnnotations.openMocks(this); + controller = new DataPipelineController(dataPipelineStatusService); + } - @AfterEach - void tearDown() throws Exception { - closeable.close(); - } + @AfterEach + void tearDown() throws Exception { + closeable.close(); + } - @Test - void testGetStatusHealth() { - final String responseBody = "Status OK"; - when(dataPipelineStatusService.getHealthStatus()).thenReturn(ResponseEntity.ok(responseBody)); + @Test + void testGetStatusHealth() { + final String responseBody = "Status OK"; + when(dataPipelineStatusService.getHealthStatus()).thenReturn(ResponseEntity.ok(responseBody)); - ResponseEntity response = controller.getDataPipelineStatusHealth(); + ResponseEntity response = controller.getDataPipelineStatusHealth(); - verify(dataPipelineStatusService).getHealthStatus(); - assertNotNull(response); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(responseBody, response.getBody()); - } -} \ No newline at end of file + verify(dataPipelineStatusService).getHealthStatus(); + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(responseBody, response.getBody()); + } +} diff --git a/liquibase-service/src/test/java/gov/cdc/etldatapipeline/service/DataPipelineStatusServiceTest.java b/liquibase-service/src/test/java/gov/cdc/etldatapipeline/service/DataPipelineStatusServiceTest.java index 151d68bb7..2ab4441a5 100644 --- a/liquibase-service/src/test/java/gov/cdc/etldatapipeline/service/DataPipelineStatusServiceTest.java +++ b/liquibase-service/src/test/java/gov/cdc/etldatapipeline/service/DataPipelineStatusServiceTest.java @@ -6,10 +6,9 @@ class DataPipelineStatusServiceTest { - @Test - void statusTest() { - DataPipelineStatusService statusService = new DataPipelineStatusService(); - Assertions.assertEquals(HttpStatus.OK, statusService.getHealthStatus().getStatusCode()); - } + @Test + void statusTest() { + DataPipelineStatusService statusService = new DataPipelineStatusService(); + Assertions.assertEquals(HttpStatus.OK, statusService.getHealthStatus().getStatusCode()); + } } - From c614ceb25862e71301e16c4fa196ad6c80809520 Mon Sep 17 00:00:00 2001 From: Eric Buckley Date: Tue, 10 Mar 2026 07:02:44 -0700 Subject: [PATCH 3/3] adding spotlessApply note for compile java task --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 70c9a6285..6a8dfb858 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ To automatically apply formatting fixes: ./gradlew spotlessApply ``` +**NOTE:** Spotless will automatically be applied after you compile your code with `./gradlew compileJava` + ### Formatting Specific Files or Directories To run Spotless on a specific file or set of files, you can use the `-PspotlessFiles` property. Providing this property automatically bypasses the incremental (`ratchetFrom`) check, allowing you to force-format existing files.