Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync for postfunding status #243

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci-cd-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
cache: maven
distribution: temurin
java-version: 11
java-version: 17

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
Expand Down Expand Up @@ -102,7 +102,7 @@ jobs:
with:
cache: maven
distribution: temurin
java-version: 11
java-version: 17

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
Expand Down
15 changes: 11 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</parent>
<groupId>uk.nhs.tis</groupId>
<artifactId>sync</artifactId>
<version>1.21.0</version>
<version>1.22.0</version>
<packaging>jar</packaging>
<name>sync</name>
<description>Separate Microservice for synchronisation</description>
Expand Down Expand Up @@ -48,12 +48,12 @@
<dependency>
<groupId>uk.nhs.tis</groupId>
<artifactId>tcs-persistence</artifactId>
<version>2.23.1</version>
<version>2.32.0</version>
</dependency>
<dependency>
<groupId>com.transformuk.hee.tis</groupId>
<artifactId>tcs-client</artifactId>
<version>6.1.0</version>
<version>6.2.2</version>
</dependency>
<dependency>
<groupId>com.transformuk.hee</groupId>
Expand Down Expand Up @@ -235,7 +235,7 @@
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<version>1.18.20</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
Expand Down Expand Up @@ -463,4 +463,11 @@
</dependencies>
</profile>
</profiles>
<distributionManagement>
<repository>
<id>codeartifact</id>
<name>CodeArtifact</name>
<url>https://hee-430723991443.d.codeartifact.eu-west-1.amazonaws.com/maven/Health-Education-England/</url>
</repository>
</distributionManagement>
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
</project>
21 changes: 19 additions & 2 deletions src/main/java/uk/nhs/tis/sync/api/JobResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import uk.nhs.tis.sync.job.PersonPlacementTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.PersonRecordStatusJob;
import uk.nhs.tis.sync.job.PostEmployingBodyTrustJob;
import uk.nhs.tis.sync.job.PostFundingSyncJob;
import uk.nhs.tis.sync.job.PostTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.RunnableJob;
import uk.nhs.tis.sync.job.person.PersonElasticSearchSyncJob;
Expand Down Expand Up @@ -47,6 +48,7 @@ public class JobResource {
private final PersonRecordStatusJob personRecordStatusJob;
private RevalCurrentPmSyncJob revalCurrentPmSyncJob;
private RevalCurrentPlacementSyncJob revalCurrentPlacementSyncJob;
private PostFundingSyncJob postFundingSyncJob;
@Autowired
private JobRunningListener jobRunningListener;
@Value("${spring.profiles.active:}")
Expand Down Expand Up @@ -79,11 +81,16 @@ public void setRevalCurrentPlacementSyncJob(
this.revalCurrentPlacementSyncJob = revalCurrentPlacementSyncJob;
}

@Autowired(required = false)
public void setPostFundingSyncJob(PostFundingSyncJob postFundingSyncJob) {
this.postFundingSyncJob = postFundingSyncJob;
}

jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
/**
* GET /jobs/status : Get all the status of 8 jobs.
*
* @return map of the status for most jobs. eg. {"personPlacementEmployingBodyTrustJob", "true"},
* which means personPlacementEmployingBodyTrustJob is currently running.
* which means personPlacementEmployingBodyTrustJob is currently running.
*/
@GetMapping("/jobs/status")
@PreAuthorize("hasPermission('tis:sync::jobs:', 'View')")
Expand All @@ -104,6 +111,9 @@ public ResponseEntity<Map<String, Boolean>> getStatus() {
if (revalCurrentPlacementSyncJob != null) {
statusMap.put("revalCurrentPlacementJob", revalCurrentPlacementSyncJob.isCurrentlyRunning());
}
if (postFundingSyncJob != null) {
statusMap.put("postFundingSyncJob", postFundingSyncJob.isCurrentlyRunning());
}
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
return ResponseEntity.ok().body(statusMap);
}

Expand All @@ -128,7 +138,7 @@ public ResponseEntity<Void> runJobsSequentially() {
*
* @param name the name of the job to run
* @return status of the requested job : "Already running" - the job has been running before
* triggering it "Just started" - the job has been started by this request
* triggering it "Just started" - the job has been started by this request
*/
@PutMapping("/job/{name}")
@PreAuthorize("hasPermission('tis:sync::jobs:', 'Update')")
Expand Down Expand Up @@ -179,6 +189,13 @@ public ResponseEntity<String> runJob(@PathVariable String name,
return ResponseEntity.badRequest().body(JOB_NOT_FOUND);
}
break;
case "postFundingSyncJob":
if (postFundingSyncJob != null) {
status = ensureRunning(postFundingSyncJob, params);
} else {
return ResponseEntity.badRequest().body(JOB_NOT_FOUND);
}
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
break;
default:
return ResponseEntity.badRequest().body(JOB_NOT_FOUND);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import uk.nhs.tis.sync.job.PersonPlacementTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.PersonRecordStatusJob;
import uk.nhs.tis.sync.job.PostEmployingBodyTrustJob;
import uk.nhs.tis.sync.job.PostFundingSyncJob;
import uk.nhs.tis.sync.job.PostTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.person.PersonElasticSearchSyncJob;
import uk.nhs.tis.sync.job.reval.RevalCurrentPmSyncJob;
Expand Down Expand Up @@ -50,6 +51,8 @@ public class JobRunningListener implements ApplicationListener<ApplicationReadyE

private RevalCurrentPmSyncJob revalCurrentPmSyncJob;

private PostFundingSyncJob postFundingSyncJob;

private LocalTime earliest;

private LocalTime latest;
Expand All @@ -59,6 +62,11 @@ public void setRevalCurrentPmSyncJob(RevalCurrentPmSyncJob revalCurrentPmSyncJob
this.revalCurrentPmSyncJob = revalCurrentPmSyncJob;
}

@Autowired(required = false)
public void setPostFundingSyncJob(PostFundingSyncJob postFundingSyncJob) {
this.postFundingSyncJob = postFundingSyncJob;
}
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved

@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
LOG.debug("Received event for an {}", event.getClass().getSimpleName());
Expand Down Expand Up @@ -105,6 +113,12 @@ public void runJobs() {
Thread.sleep(SLEEP_DURATION);
} while (revalCurrentPmSyncJob.isCurrentlyRunning());
}
if (postFundingSyncJob != null) {
postFundingSyncJob.postFundingSyncJob();
do {
Thread.sleep(SLEEP_DURATION);
} while (postFundingSyncJob.isCurrentlyRunning());
}
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
} catch (InterruptedException e) {
LOG.error(e.getMessage(), e);
}
Expand Down
92 changes: 92 additions & 0 deletions src/main/java/uk/nhs/tis/sync/job/PostFundingSyncJob.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package uk.nhs.tis.sync.job;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.transformuk.hee.tis.tcs.api.enumeration.Status;
import com.transformuk.hee.tis.tcs.service.model.Post;
import com.transformuk.hee.tis.tcs.service.model.PostFunding;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.persistence.EntityManager;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
* Job for updating funding status for posts. This class extends the
* PersonDateChangeCaptureSyncJobTemplate and provides functionality to synchronize post funding
Comment on lines +36 to +37
Copy link
Contributor

Choose a reason for hiding this comment

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

N.B. Adding a reminder comment to check that the comment is updated to match the class relationship.

* status based on specified criteria.
*/
@Component
@ManagedResource(objectName = "sync.mbean:name=PostFundingSyncJob",
description = "Job for updating funding status for posts")
public class PostFundingSyncJob extends PersonDateChangeCaptureSyncJobTemplate<Post> {
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved

private static final Logger LOG = LoggerFactory.getLogger(PostFundingSyncJob.class);

private static final String BASE_QUERY = "SELECT DISTINCT p.id FROM Post p"
+ " JOIN ( SELECT postId FROM PostFunding"
+ " WHERE startDate IS NOT NULL AND (endDate = ':endDate' OR endDate IS NULL)"
+ " GROUP BY postId"
+ " HAVING COUNT(id) > 0)"
+ " pf ON p.id = pf.postId ORDER BY p.id LIMIT :pageSize";
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved

private final ObjectMapper objectMapper;
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved

public PostFundingSyncJob(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@Override
public void run(String params) {
postFundingSyncJob();
}

@Scheduled(cron = "${application.cron.postFundingSyncJob}")
@ManagedOperation(description = "update post funding status")
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
public void postFundingSyncJob() {
super.runSyncJob(null);
}

@Override
protected String buildQueryForDate(LocalDate dateOfChange) {
String endDate = dateOfChange.minusDays(1).format(DateTimeFormatter.ISO_LOCAL_DATE);
return BASE_QUERY.replace(":endDate", endDate).replace(":pageSize", "" + DEFAULT_PAGE_SIZE);
}

@Override
protected int convertData(Set<Post> entitiesToSave, List<Long> entityData,
EntityManager entityManager) {
int entities = entityData.size();
entityData.stream()
.map(id -> entityManager.find(Post.class, id))
.filter(Objects::nonNull)
.forEach(post -> {
// check if the post has multiple post fundings
Set<PostFunding> postFundings = post.getFundings();
if (postFundings.size() > 1) {
// if the post has multiple post fundings, do nothing
entitiesToSave.add(post);
Copy link
Contributor

Choose a reason for hiding this comment

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

If we want to 'do nothing', why add this to the lists of Posts to save instead of ignoring it?

} else if (postFundings.size() == 1) {
// if the post has a single post funding, set its status to "INACTIVE"
post.setFundingStatus(Status.INACTIVE);
entitiesToSave.add(post);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Please ensure sufficient coverage of test cases, e.g. I'm surprised if 1 Post Funding starting 1970 with no end date should be considered Status.INACTIVE.

});
return entities - entitiesToSave.size();
}

@Override
protected void handleData(Set<Post> dataToSave, EntityManager entityManager) {
if (CollectionUtils.isNotEmpty(dataToSave)) {
dataToSave.forEach(entityManager::persist);
entityManager.flush();
}
}
}
1 change: 1 addition & 0 deletions src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ application:
revalCurrentPmJob: ${APPLICATION_CRON_REVALCURRENTPMJOB:0 39 1 * * *}
revalCurrentPlacementJob: ${APPLICATION_CRON_REVALCURRENTPLACEMENTJOB:0 49 1 * * *}
recordResendingJob: ${APPLICATION_CRON_RECORDRESENDINGJOB}
postFundingSyncJob: ${APPLICATION_CRON_POSTFUNDINGSYNCJOB:0 0 3 * * *}
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved
jobs:
runOnStartup:
earliest: ${APPLICATION_JOBS_RUNONSTARTUP_EARLIEST:00:00}
Expand Down
5 changes: 5 additions & 0 deletions src/main/resources/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ <h2 class="box-1">Person Record Status Sync</h2>
<button type="button" id="personRecordStatusJob" class="box-2">Run job</button>
<p id="personRecordStatusJob_status" class="box-3"></p>
</div>
<div class="container-8">
<h2 class="box-1">Post Funding Sync Job</h2>
<button type="button" id="postFundingSyncJob" class="box-2">Run job</button>
<p id="postFundingSyncJob_status" class="box-3"></p>
</div>
</div>
<br>
<div id="reval-panel" class="hidden">
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ function registerListeners() {
document.getElementById("postTrainingBodyTrustJob").addEventListener("click", runJob);
document.getElementById("personElasticSearchSyncJob").addEventListener("click", runJob);
document.getElementById("personRecordStatusJob").addEventListener("click", runPersonStatusSyncJob);
document.getElementById("postFundingSyncJob").addEventListener("click", runJob);
document.getElementById("runAllJobs").addEventListener("click", runAllJobs);
document.getElementById("getStatus").addEventListener("click", getStatus);
document.getElementById("revalCurrentPmJob").addEventListener("click", runJob);
Expand Down
28 changes: 26 additions & 2 deletions src/test/java/uk/nhs/tis/sync/api/JobResourceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import uk.nhs.tis.sync.job.PersonPlacementTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.PersonRecordStatusJob;
import uk.nhs.tis.sync.job.PostEmployingBodyTrustJob;
import uk.nhs.tis.sync.job.PostFundingSyncJob;
import uk.nhs.tis.sync.job.PostTrainingBodyTrustJob;
import uk.nhs.tis.sync.job.person.PersonElasticSearchSyncJob;
import uk.nhs.tis.sync.job.reval.RevalCurrentPlacementSyncJob;
Expand Down Expand Up @@ -66,6 +67,9 @@ class JobResourceTest {
@MockBean
private RevalCurrentPlacementSyncJob revalCurrentPlacementSyncJob;

@MockBean
PostFundingSyncJob postFundingSyncJob;

private MockMvc mockMvc;

private JobResource jobResource;
Expand All @@ -81,6 +85,7 @@ void setup() {
personRecordStatusJob);
jobResource.setRevalCurrentPmSyncJob(revalCurrentPmSyncJob);
jobResource.setRevalCurrentPlacementSyncJob((revalCurrentPlacementSyncJob));
jobResource.setPostFundingSyncJob(postFundingSyncJob);
mockMvc = MockMvcBuilders.standaloneSetup(jobResource).build();
}

Expand All @@ -103,6 +108,8 @@ void shouldReturnAllStatusWhenGetStatus() throws Exception {
.thenReturn(false);
when(revalCurrentPmSyncJob.isCurrentlyRunning())
.thenReturn(false);
when(postFundingSyncJob.isCurrentlyRunning())
.thenReturn(false);

mockMvc.perform(get("/api/jobs/status")
.contentType(MediaType.APPLICATION_JSON))
Expand All @@ -114,6 +121,7 @@ void shouldReturnAllStatusWhenGetStatus() throws Exception {
.andExpect(jsonPath("$.personOwnerRebuildJob").value(false))
.andExpect(jsonPath("$.personRecordStatusJob").value(false))
.andExpect(jsonPath("$.revalCurrentPmJob").value(false))
.andExpect(jsonPath("$.postFundingSyncJob").value(false))
.andExpect(status().isOk());
}

Expand Down Expand Up @@ -159,7 +167,8 @@ void shouldNotReturnStatusForRevalCurrentSyncWhenJobNotAvailable() throws Except
"personElasticSearchSyncJob",
"personOwnerRebuildJob",
"personRecordStatusJob",
"revalCurrentPmJob"
"revalCurrentPmJob",
"postFundingSyncJob"
})
void shouldReturnJustStartedWhenAJobTriggered(String name) throws Exception {
when(personPlacementTrainingBodyTrustJob.isCurrentlyRunning())
Expand All @@ -178,6 +187,8 @@ void shouldReturnJustStartedWhenAJobTriggered(String name) throws Exception {
.thenReturn(false);
when(revalCurrentPmSyncJob.isCurrentlyRunning())
.thenReturn(false);
when(postFundingSyncJob.isCurrentlyRunning())
.thenReturn(false);

mockMvc.perform(put("/api/job/" + name)
.contentType(MediaType.APPLICATION_JSON))
Expand Down Expand Up @@ -205,6 +216,16 @@ void shouldReturnErrorWhenRevalCurrentPlacementSyncIsTriggeredButNotAvailable()
.andExpect(jsonPath("$.error").value("Job not found"));
}

@Test
void shouldReturnErrorWhenPostFundingSyncJobIsTriggeredButNotAvailable() throws Exception {
jobResource.setPostFundingSyncJob(null);

mockMvc.perform(put("/api/job/postFundingSyncJob")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.error").value("Job not found"));
}
jayanta2018 marked this conversation as resolved.
Show resolved Hide resolved

@DisplayName("run personRecordStatusJob with correct date argument")
@ParameterizedTest(name = "Should return 'Just started' status when personRecordStatusJob is triggered with \"{0}\".")
@ValueSource(strings = {
Expand Down Expand Up @@ -261,7 +282,8 @@ void shouldReturnErrorWhenPersonRecordStatusJobWithInvalidArg(String arg)
"personElasticSearchSyncJob",
"personOwnerRebuildJob",
"personRecordStatusJob",
"revalCurrentPmJob"
"revalCurrentPmJob",
"postFundingSyncJob"
})
void shouldReturnAlreadyRunningWhenTriggerARunningJob(String name) throws Exception {

Expand All @@ -281,6 +303,8 @@ void shouldReturnAlreadyRunningWhenTriggerARunningJob(String name) throws Except
.thenReturn(true);
when(revalCurrentPmSyncJob.isCurrentlyRunning())
.thenReturn(true);
when(postFundingSyncJob.isCurrentlyRunning())
.thenReturn(true);

mockMvc.perform(put("/api/job/" + name)
.contentType(MediaType.APPLICATION_JSON))
Expand Down
Loading
Loading