diff --git a/README.md b/README.md index b54301e2..16ef9a3c 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,235 @@ $ mvn clean spring-boot:run ### Production ```bash $ mvn clean package -DskipTests -Dproduction +``` + +## Rest API + +| **Title** | **Projects** | +| :------------------- | :------------------------------------------------------------------------------------------ | +| **Description** | Returns a list of all projects. | +| **URL** | ```/projects``` | +| **Method** | **GET** | +| **URL Parameters** | | +| **Success Response** | **Code:** 200 OK
**Content Type:** application/json
| +| **Sample Request** | ```/projects``` | +| **Notes** | These are managed projects for this service and not projects from a remote project manager. | + +```json +{ + "meta": { + "status": "SUCCESS", + "action": null, + "message": "Your request was successful", + "id": null + }, + "payload": { + "ArrayList": [ + { + "id": 1, + "name": "Legacy DSpace", + "scopeId": "1934", + "remoteProjectManager": { + "id": 1, + "name": "VersionOne", + "type": "VERSION_ONE" + } + }, + { + "id": 2, + "name": "Code Management - Maps", + "scopeId": "3781", + "remoteProjectManager": { + "id": 1, + "name": "VersionOne", + "type": "VERSION_ONE" + } + }, + { + "id": 3, + "name": "CORAL - Electronic Resource Management", + "scopeId": "3783", + "remoteProjectManager": { + "id": 1, + "name": "VersionOne", + "type": "VERSION_ONE" + } + }, + { + "id": 4, + "name": "Piper - Automated Ingest", + "scopeId": "3786", + "remoteProjectManager": { + "id": 1, + "name": "VersionOne", + "type": "VERSION_ONE" + } + } + ] + } +} +``` + +
+ +| **Title** | **Active Sprints** | +| :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Description** | Returns a list of all active sprints based on associated remote project manager projects. | +| **URL** | ```/sprints/active``` | +| **Method** | **GET** | +| **URL Parameters** | | +| **Success Response** | **Code:** 200 OK
**Content Type:** application/json
**Schema:** | +| **Sample Request** | ```/sprints/active``` | +| **Notes** | Currently, only remote project manager implemented is VersionOne. VersionOne sprints are based on a timebox which is a spring schedule in the UI. Projects can share the same sprint schedule and will appear to be the same sprint in this response. | + +```json +{ + "meta": { + "status": "SUCCESS", + "action": null, + "message": "Your request was successful", + "id": null + }, + "payload": { + "ArrayList": [ + { + "id": "8416", + "name": "Sprint 14", + "project": "CORAL - Electronic Resource Management", + "cards": [ + { + "id": "8234", + "number": "B-03467", + "type": "Feature", + "status": "Accepted", + "name": "Update new feedback and purchase forms to incorporate all fields from the existing feedback form", + "description": "

Needs to have the new styling and needs to pass WAVE ADA check.

\n

 

\n

All the key/value information will go right into the Notes field.

\n

 

\n

We will be finishing out and fully styling the feedback form, the request a purchase form, and ideally the https://coral.library.tamu.edu/resourcelink.php?resource=1440 link resolver while we're at it.

", + "assignees": [ + { + "id": "20", + "name": "Jeremy Huff", + "avatar": "1706" + }, + { + "id": "3483", + "name": "Jason Savell", + "avatar": "no_avatar.png" + }, + { + "id": "7888", + "name": "Kevin Day", + "avatar": "no_avatar.png" + } + ] + }, + { + "id": "8417", + "number": "B-03578", + "type": "Feature", + "status": "Done", + "name": "Sort by title by default when viewing resource list with trial/purchase requests hidden", + "assignees": [ + { + "id": "3483", + "name": "Jason Savell", + "avatar": "no_avatar.png" + } + ] + } + ] + }, + { + "id": "8435", + "name": "Weaver Upgrades/Auth2 Retirement", + "project": "DI Internal", + "cards": [ + { + "id": "8436", + "number": "B-03587", + "type": "Feature", + "status": "Done", + "name": "Upgrade My Library UI to weaver-ui 2", + "estimate": 3, + "assignees": [ + { + "id": "6616", + "name": "Ryan Laddusaw", + "avatar": "no_avatar.png" + } + ] + }, + { + "id": "8437", + "number": "B-03588", + "type": "Feature", + "status": "Done", + "name": "Update My Library Service to Weaver 2", + "estimate": 2, + "assignees": [ + { + "id": "6616", + "name": "Ryan Laddusaw", + "avatar": "no_avatar.png" + } + ] + } + ] + } + ] + } +} +``` + +
+ +| **Title** | **Projects Stats** | +| :------------------- | :------------------------------------------------------------------------------------------------------------------ | +| **Description** | Returns a list of all projects and there statistics gathered from their associated remote project manager projects. | +| **URL** | ```/projects/stats``` | +| **Method** | **GET** | +| **URL Parameters** | | +| **Success Response** | **Code:** 200 OK
**Content Type:** application/json | +| **Sample Request** | ```/projects/stats``` | +| **Notes** | | + +```json +{ + "meta": { + "status": "SUCCESS", + "action": null, + "message": "Your request was successful", + "id": null + }, + "payload": { + "ArrayList": [ + { + "id": "1", + "name": "Legacy DSpace", + "requestCount": 22, + "issueCount": 41, + "featureCount": 32, + "defectCount": 0, + "backlogItemCount": 32 + }, + { + "id": "2", + "name": "Code Management - Maps", + "requestCount": 0, + "issueCount": 0, + "featureCount": 5, + "defectCount": 0, + "backlogItemCount": 5 + }, + { + "id": "3", + "name": "CORAL - Electronic Resource Management", + "requestCount": 2, + "issueCount": 0, + "featureCount": 12, + "defectCount": 8, + "backlogItemCount": 20 + } + ] + } +} ``` \ No newline at end of file diff --git a/src/main/java/edu/tamu/app/cache/ProjectsStatsCache.java b/src/main/java/edu/tamu/app/cache/ProjectsStatsCache.java new file mode 100644 index 00000000..b848fa9a --- /dev/null +++ b/src/main/java/edu/tamu/app/cache/ProjectsStatsCache.java @@ -0,0 +1,14 @@ +package edu.tamu.app.cache; + +import java.util.ArrayList; +import java.util.List; + +import edu.tamu.app.cache.model.ProjectStats; + +public class ProjectsStatsCache extends AbstractCache> { + + public ProjectsStatsCache() { + set(new ArrayList()); + } + +} diff --git a/src/main/java/edu/tamu/app/cache/controller/ActiveSprintsCacheController.java b/src/main/java/edu/tamu/app/cache/controller/ActiveSprintsCacheController.java index c8308979..6bf9e34c 100644 --- a/src/main/java/edu/tamu/app/cache/controller/ActiveSprintsCacheController.java +++ b/src/main/java/edu/tamu/app/cache/controller/ActiveSprintsCacheController.java @@ -6,7 +6,7 @@ import edu.tamu.app.cache.service.ActiveSprintsScheduledCacheService; @RestController -@RequestMapping("/active-sprints") +@RequestMapping("/sprints/active") public class ActiveSprintsCacheController extends AbstractCacheController { } diff --git a/src/main/java/edu/tamu/app/cache/controller/ProjectsStatsCacheController.java b/src/main/java/edu/tamu/app/cache/controller/ProjectsStatsCacheController.java new file mode 100644 index 00000000..0c0bf5e2 --- /dev/null +++ b/src/main/java/edu/tamu/app/cache/controller/ProjectsStatsCacheController.java @@ -0,0 +1,12 @@ +package edu.tamu.app.cache.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import edu.tamu.app.cache.service.ProjectsStatsScheduledCacheService; + +@RestController +@RequestMapping("/projects/stats") +public class ProjectsStatsCacheController extends AbstractCacheController { + +} diff --git a/src/main/java/edu/tamu/app/cache/controller/RemoteProjectsCacheController.java b/src/main/java/edu/tamu/app/cache/controller/RemoteProjectsCacheController.java index 16c2f986..a4bba23b 100644 --- a/src/main/java/edu/tamu/app/cache/controller/RemoteProjectsCacheController.java +++ b/src/main/java/edu/tamu/app/cache/controller/RemoteProjectsCacheController.java @@ -6,7 +6,7 @@ import edu.tamu.app.cache.service.RemoteProjectsScheduledCacheService; @RestController -@RequestMapping("/remote-projects") +@RequestMapping("/projects/remote") public class RemoteProjectsCacheController extends AbstractCacheController { } diff --git a/src/main/java/edu/tamu/app/cache/model/ProjectStats.java b/src/main/java/edu/tamu/app/cache/model/ProjectStats.java new file mode 100644 index 00000000..43c9c50a --- /dev/null +++ b/src/main/java/edu/tamu/app/cache/model/ProjectStats.java @@ -0,0 +1,69 @@ +package edu.tamu.app.cache.model; + +import java.io.Serializable; + +public class ProjectStats implements Serializable { + + private static final long serialVersionUID = -1622544796949909087L; + + private final String id; + + private final String name; + + private final int requestCount; + + private final int issueCount; + + private final int featureCount; + + private final int defectCount; + + public ProjectStats() { + super(); + id = ""; + name = ""; + requestCount = 0; + issueCount = 0; + featureCount = 0; + defectCount = 0; + } + + public ProjectStats(String id, String name, int requestCount, int issueCount, int featureCount, int defectCount) { + super(); + this.id = id; + this.name = name; + this.requestCount = requestCount; + this.issueCount = issueCount; + this.featureCount = featureCount; + this.defectCount = defectCount; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public int getRequestCount() { + return requestCount; + } + + public int getIssueCount() { + return issueCount; + } + + public int getFeatureCount() { + return featureCount; + } + + public int getDefectCount() { + return defectCount; + } + + public int getBacklogItemCount() { + return featureCount + defectCount; + } + +} diff --git a/src/main/java/edu/tamu/app/cache/model/RemoteProject.java b/src/main/java/edu/tamu/app/cache/model/RemoteProject.java index 8e537f21..fa37d6ea 100644 --- a/src/main/java/edu/tamu/app/cache/model/RemoteProject.java +++ b/src/main/java/edu/tamu/app/cache/model/RemoteProject.java @@ -1,69 +1,15 @@ package edu.tamu.app.cache.model; -import java.io.Serializable; - -public class RemoteProject implements Serializable { +public class RemoteProject extends ProjectStats { private static final long serialVersionUID = 8384046327331854613L; - private final String scopeId; - - private final String name; - - private final int requestCount; - - private final int issueCount; - - private final int storyCount; - - private final int defectCount; - public RemoteProject() { super(); - scopeId = ""; - name = ""; - requestCount = 0; - issueCount = 0; - storyCount = 0; - defectCount = 0; - } - - public RemoteProject(String scopeId, String name, int requestCount, int issueCount, int storyCount, int defectCount) { - super(); - this.scopeId = scopeId; - this.name = name; - this.requestCount = requestCount; - this.issueCount = issueCount; - this.storyCount = storyCount; - this.defectCount = defectCount; - } - - public String getScopeId() { - return scopeId; - } - - public String getName() { - return name; - } - - public int getRequestCount() { - return requestCount; - } - - public int getIssueCount() { - return issueCount; - } - - public int getStoryCount() { - return storyCount; - } - - public int getDefectCount() { - return defectCount; } - public int getBacklogItemCount() { - return storyCount + defectCount; + public RemoteProject(String id, String name, int requestCount, int issueCount, int featureCount, int defectCount) { + super(id, name, requestCount, issueCount, featureCount, defectCount); } } diff --git a/src/main/java/edu/tamu/app/cache/service/AbstractProjectScheduledCacheService.java b/src/main/java/edu/tamu/app/cache/service/AbstractProjectScheduledCacheService.java new file mode 100644 index 00000000..25db5833 --- /dev/null +++ b/src/main/java/edu/tamu/app/cache/service/AbstractProjectScheduledCacheService.java @@ -0,0 +1,11 @@ +package edu.tamu.app.cache.service; + +import edu.tamu.app.cache.Cache; + +public abstract class AbstractProjectScheduledCacheService> extends AbstractScheduledCacheService implements ProjectScheduledCache { + + public AbstractProjectScheduledCacheService(C cache) { + super(cache); + } + +} diff --git a/src/main/java/edu/tamu/app/cache/service/ActiveSprintsScheduledCacheService.java b/src/main/java/edu/tamu/app/cache/service/ActiveSprintsScheduledCacheService.java index 178a6481..7d701221 100644 --- a/src/main/java/edu/tamu/app/cache/service/ActiveSprintsScheduledCacheService.java +++ b/src/main/java/edu/tamu/app/cache/service/ActiveSprintsScheduledCacheService.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -13,13 +14,14 @@ import edu.tamu.app.cache.ActiveSprintsCache; import edu.tamu.app.cache.model.Sprint; +import edu.tamu.app.model.Project; import edu.tamu.app.model.RemoteProjectManager; import edu.tamu.app.model.repo.ProjectRepo; import edu.tamu.app.service.manager.RemoteProjectManagerBean; import edu.tamu.weaver.response.ApiResponse; @Service -public class ActiveSprintsScheduledCacheService extends AbstractScheduledCacheService, ActiveSprintsCache> { +public class ActiveSprintsScheduledCacheService extends AbstractProjectScheduledCacheService, ActiveSprintsCache> { private static final Logger logger = Logger.getLogger(ActiveSprintsScheduledCacheService.class); @@ -40,15 +42,7 @@ public void update() { logger.info("Caching active sprints..."); List activeSprints = new ArrayList(); projectRepo.findAll().forEach(project -> { - Optional remoteProjectManager = Optional.ofNullable(project.getRemoteProjectManager()); - if (remoteProjectManager.isPresent()) { - RemoteProjectManagerBean remoteProjectManagerBean = (RemoteProjectManagerBean) managementBeanRegistry.getService(remoteProjectManager.get().getName()); - try { - activeSprints.addAll(remoteProjectManagerBean.getActiveSprintsByProjectId(project.getScopeId())); - } catch (Exception e) { - e.printStackTrace(); - } - } + activeSprints.addAll(fetchActiveSprints(project)); }); set(activeSprints); logger.info("Finished caching active sprints"); @@ -56,7 +50,41 @@ public void update() { public void broadcast() { logger.info("Broadcasting cached active sprints"); - simpMessagingTemplate.convertAndSend("/channel/active-sprints", new ApiResponse(SUCCESS, get())); + simpMessagingTemplate.convertAndSend("/channel/sprints/active", new ApiResponse(SUCCESS, get())); + } + + public void addProject(Project project) { + List activeSprints = get(); + activeSprints.addAll(fetchActiveSprints(project)); + set(activeSprints); + broadcast(); + } + + public void updateProject(Project project) { + List activeSprints = get().stream().filter(as -> !as.getProject().equals(project.getName())).collect(Collectors.toList()); + activeSprints.addAll(fetchActiveSprints(project)); + set(activeSprints); + broadcast(); + } + + public void removeProject(Project project) { + List activeSprints = get().stream().filter(p -> !p.getProject().equals(project.getName())).collect(Collectors.toList()); + set(activeSprints); + broadcast(); + } + + private List fetchActiveSprints(Project project) { + List activeSprints = new ArrayList(); + Optional remoteProjectManager = Optional.ofNullable(project.getRemoteProjectManager()); + if (remoteProjectManager.isPresent()) { + RemoteProjectManagerBean remoteProjectManagerBean = (RemoteProjectManagerBean) managementBeanRegistry.getService(remoteProjectManager.get().getName()); + try { + activeSprints.addAll(remoteProjectManagerBean.getActiveSprintsByProjectId(project.getScopeId())); + } catch (Exception e) { + e.printStackTrace(); + } + } + return activeSprints; } } diff --git a/src/main/java/edu/tamu/app/cache/service/ProjectScheduledCache.java b/src/main/java/edu/tamu/app/cache/service/ProjectScheduledCache.java new file mode 100644 index 00000000..f95be579 --- /dev/null +++ b/src/main/java/edu/tamu/app/cache/service/ProjectScheduledCache.java @@ -0,0 +1,14 @@ +package edu.tamu.app.cache.service; + +import edu.tamu.app.cache.Cache; +import edu.tamu.app.model.Project; + +public interface ProjectScheduledCache> extends ScheduledCache { + + public void addProject(Project project); + + public void updateProject(Project project); + + public void removeProject(Project project); + +} diff --git a/src/main/java/edu/tamu/app/cache/service/ProjectsStatsScheduledCacheService.java b/src/main/java/edu/tamu/app/cache/service/ProjectsStatsScheduledCacheService.java new file mode 100644 index 00000000..2e572f24 --- /dev/null +++ b/src/main/java/edu/tamu/app/cache/service/ProjectsStatsScheduledCacheService.java @@ -0,0 +1,104 @@ +package edu.tamu.app.cache.service; + +import static edu.tamu.weaver.response.ApiStatus.SUCCESS; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import edu.tamu.app.cache.ProjectsStatsCache; +import edu.tamu.app.cache.model.ProjectStats; +import edu.tamu.app.cache.model.RemoteProject; +import edu.tamu.app.model.Project; +import edu.tamu.app.model.RemoteProjectManager; +import edu.tamu.app.model.repo.ProjectRepo; +import edu.tamu.weaver.response.ApiResponse; + +@Service +public class ProjectsStatsScheduledCacheService extends AbstractProjectScheduledCacheService, ProjectsStatsCache> { + + private static final Logger logger = Logger.getLogger(ProjectsStatsScheduledCacheService.class); + + @Autowired + private ProjectRepo projectRepo; + + @Autowired + private RemoteProjectsScheduledCacheService remoteProjectsScheduledCacheService; + + public ProjectsStatsScheduledCacheService() { + super(new ProjectsStatsCache()); + } + + @Override + @Scheduled(initialDelayString = "${app.cache.projects-stats.delay}", fixedDelayString = "${app.cache.projects-stats.interval}") + public void schedule() { + super.schedule(); + } + + public void update() { + logger.info("Caching projects stats..."); + List projectsStats = new ArrayList(); + projectRepo.findAll().forEach(project -> { + projectsStats.add(getProjectStats(project)); + }); + set(projectsStats); + logger.info("Finished caching projects stats"); + } + + public void broadcast() { + logger.info("Broadcasting cached projects stats"); + simpMessagingTemplate.convertAndSend("/channel/projects/stats", new ApiResponse(SUCCESS, get())); + } + + public void addProject(Project project) { + List projectsStats = get(); + projectsStats.add(getProjectStats(project)); + set(projectsStats); + broadcast(); + } + + public void updateProject(Project project) { + List projectsStats = get().stream().filter(p -> !p.getId().equals(project.getId())).collect(Collectors.toList()); + projectsStats.add(getProjectStats(project)); + set(projectsStats); + broadcast(); + } + + public void removeProject(Project project) { + List projectsStats = get().stream().filter(p -> !p.getId().equals(project.getId())).collect(Collectors.toList()); + set(projectsStats); + broadcast(); + } + + private ProjectStats getProjectStats(Project project) { + String id = project.getId().toString(); + String name = project.getName(); + int requestCount = 0; + int issueCount = 0; + int featureCount = 0; + int defectCount = 0; + + // NOTE: if and when project can be associated to multiple remote projects, loop here + + Optional remoteProjectManager = Optional.ofNullable(project.getRemoteProjectManager()); + Optional scopeId = Optional.ofNullable(project.getScopeId()); + if (remoteProjectManager.isPresent() && scopeId.isPresent()) { + Optional remoteProject = remoteProjectsScheduledCacheService.getRemoteProject(remoteProjectManager.get().getId(), scopeId.get()); + if (remoteProject.isPresent()) { + requestCount += remoteProject.get().getRequestCount(); + issueCount += remoteProject.get().getIssueCount(); + featureCount += remoteProject.get().getFeatureCount(); + defectCount += remoteProject.get().getDefectCount(); + } + } + + return new ProjectStats(id, name, requestCount, issueCount, featureCount, defectCount); + } + +} diff --git a/src/main/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheService.java b/src/main/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheService.java index a2b929bf..04aa5ed5 100644 --- a/src/main/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheService.java +++ b/src/main/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheService.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -57,7 +58,21 @@ public void update() { public void broadcast() { logger.info("Broadcasting cached remote projects"); - simpMessagingTemplate.convertAndSend("/channel/remote-projects", new ApiResponse(SUCCESS, get())); + simpMessagingTemplate.convertAndSend("/channel/projects/remote", new ApiResponse(SUCCESS, get())); + } + + public Optional getRemoteProject(Long remoteProjectManagerId, String scopeId) { + Optional remoteProject = Optional.empty(); + Optional> remoteProjects = Optional.ofNullable(get().get(remoteProjectManagerId)); + if (remoteProjects.isPresent()) { + for (RemoteProject rp : remoteProjects.get()) { + if (rp.getId().equals(scopeId)) { + remoteProject = Optional.of(rp); + break; + } + } + } + return remoteProject; } } diff --git a/src/main/java/edu/tamu/app/controller/ProjectController.java b/src/main/java/edu/tamu/app/controller/ProjectController.java index 60bb20cd..44c95736 100644 --- a/src/main/java/edu/tamu/app/controller/ProjectController.java +++ b/src/main/java/edu/tamu/app/controller/ProjectController.java @@ -6,6 +6,7 @@ import static edu.tamu.weaver.validation.model.BusinessValidationType.DELETE; import static edu.tamu.weaver.validation.model.BusinessValidationType.UPDATE; +import java.util.List; import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -14,12 +15,18 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import com.fasterxml.jackson.annotation.JsonView; + +import edu.tamu.app.cache.service.ProjectScheduledCache; import edu.tamu.app.model.Project; import edu.tamu.app.model.RemoteProjectManager; import edu.tamu.app.model.repo.ProjectRepo; @@ -30,6 +37,7 @@ import edu.tamu.app.service.registry.ManagementBeanRegistry; import edu.tamu.app.service.ticketing.SugarService; import edu.tamu.weaver.response.ApiResponse; +import edu.tamu.weaver.response.ApiView; import edu.tamu.weaver.validation.aspect.annotation.WeaverValidatedModel; import edu.tamu.weaver.validation.aspect.annotation.WeaverValidation; @@ -49,52 +57,71 @@ public class ProjectController { @Autowired private SugarService sugarService; + @Autowired + private List> projectSceduledCaches; + private Logger logger = LoggerFactory.getLogger(this.getClass()); - @RequestMapping(method = RequestMethod.GET) + @GetMapping + @JsonView(ApiView.Partial.class) @PreAuthorize("hasRole('ANONYMOUS')") public ApiResponse getAll() { return new ApiResponse(SUCCESS, projectRepo.findAll()); } - @RequestMapping(value = "/{id}", method = RequestMethod.GET) + @GetMapping("/{id}") + @JsonView(ApiView.Partial.class) @PreAuthorize("hasRole('ANONYMOUS')") public ApiResponse getOne(@PathVariable Long id) { return new ApiResponse(SUCCESS, projectRepo.findOne(id)); } - @RequestMapping(method = RequestMethod.POST) + @PostMapping @PreAuthorize("hasRole('USER')") @WeaverValidation(business = { @WeaverValidation.Business(value = CREATE) }) public ApiResponse createProject(@WeaverValidatedModel Project project) { logger.info("Creating Project: " + project.getName()); - return new ApiResponse(SUCCESS, projectRepo.create(project)); + reifyProjectRemoteProjectManager(project); + project = projectRepo.create(project); + for (ProjectScheduledCache projectSceduledCache : projectSceduledCaches) { + projectSceduledCache.addProject(project); + } + return new ApiResponse(SUCCESS, project); } - @RequestMapping(method = RequestMethod.PUT) + @PutMapping @PreAuthorize("hasRole('USER')") @WeaverValidation(business = { @WeaverValidation.Business(value = UPDATE) }) public ApiResponse updateProject(@WeaverValidatedModel Project project) { logger.info("Updating Project: " + project.getName()); - return new ApiResponse(SUCCESS, projectRepo.update(project)); + reifyProjectRemoteProjectManager(project); + project = projectRepo.update(project); + for (ProjectScheduledCache projectSceduledCache : projectSceduledCaches) { + projectSceduledCache.updateProject(project); + } + return new ApiResponse(SUCCESS, project); } - @RequestMapping(method = RequestMethod.DELETE) + @DeleteMapping @PreAuthorize("hasRole('USER')") @WeaverValidation(business = { @WeaverValidation.Business(value = DELETE) }) public ApiResponse deleteProject(@WeaverValidatedModel Project project) { logger.info("Deleting Project: " + project.getName()); + reifyProjectRemoteProjectManager(project); projectRepo.delete(project); + for (ProjectScheduledCache projectSceduledCache : projectSceduledCaches) { + projectSceduledCache.removeProject(project); + } return new ApiResponse(SUCCESS); } - @RequestMapping(value = "/issue", method = RequestMethod.POST) + @PostMapping("/issue") @PreAuthorize("hasRole('ANONYMOUS')") public ApiResponse submitIssueRequest(@RequestBody TicketRequest request) { return new ApiResponse(SUCCESS, sugarService.submit(request)); } - @RequestMapping(value = "/feature", method = RequestMethod.POST) + @PostMapping("/feature") @PreAuthorize("hasRole('MANAGER') or @whitelist.isAllowed(#req)") public ApiResponse pushRequest(HttpServletRequest req, @RequestBody FeatureRequest request) { Optional project = Optional.ofNullable(projectRepo.findOne(request.getProjectId())); @@ -118,7 +145,7 @@ public ApiResponse pushRequest(HttpServletRequest req, @RequestBody FeatureReque return response; } - @RequestMapping(value = "/{remoteProjectManagerId}/remote-projects", method = RequestMethod.GET) + @GetMapping("/{remoteProjectManagerId}/remote-projects") @PreAuthorize("hasRole('MANAGER')") public ApiResponse getAllRemoteProjects(@PathVariable Long remoteProjectManagerId) { Optional remoteProjectManager = Optional.ofNullable(remoteProjectManagerRepo.findOne(remoteProjectManagerId)); @@ -136,7 +163,7 @@ public ApiResponse getAllRemoteProjects(@PathVariable Long remoteProjectManagerI return response; } - @RequestMapping(value = "/{remoteProjectManagerId}/remote-projects/{scopeId}", method = RequestMethod.GET) + @GetMapping("/{remoteProjectManagerId}/remote-projects/{scopeId}") @PreAuthorize("hasRole('MANAGER')") public ApiResponse getRemoteProjectByScopeId(@PathVariable Long remoteProjectManagerId, @PathVariable String scopeId) { Optional remoteProjectManager = Optional.ofNullable(remoteProjectManagerRepo.findOne(remoteProjectManagerId)); @@ -154,4 +181,12 @@ public ApiResponse getRemoteProjectByScopeId(@PathVariable Long remoteProjectMan return response; } + private void reifyProjectRemoteProjectManager(Project project) { + Optional remoteProjectManager = Optional.ofNullable(project.getRemoteProjectManager()); + if (remoteProjectManager.isPresent()) { + Long remoteProjectManagerId = remoteProjectManager.get().getId(); + project.setRemoteProjectManager(remoteProjectManagerRepo.findOne(remoteProjectManagerId)); + } + } + } \ No newline at end of file diff --git a/src/main/java/edu/tamu/app/model/ManagementService.java b/src/main/java/edu/tamu/app/model/ManagementService.java index 52644ef2..0a12fb7f 100644 --- a/src/main/java/edu/tamu/app/model/ManagementService.java +++ b/src/main/java/edu/tamu/app/model/ManagementService.java @@ -15,17 +15,22 @@ import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchMode; +import com.fasterxml.jackson.annotation.JsonView; + import edu.tamu.app.model.converter.CryptoConverter; import edu.tamu.app.model.validation.ManagementServiceValidator; +import edu.tamu.weaver.response.ApiView; import edu.tamu.weaver.validation.model.ValidatingBaseEntity; @MappedSuperclass public abstract class ManagementService extends ValidatingBaseEntity { @Column + @JsonView(ApiView.Partial.class) protected String name; @Enumerated + @JsonView(ApiView.Partial.class) protected ServiceType type; @ElementCollection(fetch = EAGER) diff --git a/src/main/java/edu/tamu/app/model/Project.java b/src/main/java/edu/tamu/app/model/Project.java index 81f62c23..6fa1538f 100644 --- a/src/main/java/edu/tamu/app/model/Project.java +++ b/src/main/java/edu/tamu/app/model/Project.java @@ -11,25 +11,31 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonView; import edu.tamu.app.model.validation.ProjectValidator; +import edu.tamu.weaver.response.ApiView; import edu.tamu.weaver.validation.model.ValidatingBaseEntity; @Entity public class Project extends ValidatingBaseEntity { @Column(unique = true, nullable = false) + @JsonView(ApiView.Partial.class) private String name; @JsonInclude(Include.NON_NULL) @Column(nullable = true) + @JsonView(ApiView.Partial.class) private String scopeId; @JsonInclude(Include.NON_NULL) @ManyToOne(fetch = EAGER, cascade = { DETACH, REFRESH, MERGE }, optional = true) + @JsonView(ApiView.Partial.class) private RemoteProjectManager remoteProjectManager; public Project() { + super(); this.modelValidator = new ProjectValidator(); } diff --git a/src/main/java/edu/tamu/app/service/ticketing/SugarService.java b/src/main/java/edu/tamu/app/service/ticketing/SugarService.java index 54e49980..f044ba2c 100644 --- a/src/main/java/edu/tamu/app/service/ticketing/SugarService.java +++ b/src/main/java/edu/tamu/app/service/ticketing/SugarService.java @@ -16,15 +16,14 @@ public class SugarService implements TicketManagementSoftwareBean { @Autowired private EmailSender emailService; - // NOTE: using reporting email as it is the same - @Value("${app.reporting.address}") - private String reportingAddress; + @Value("${app.sugar.email:helpdesk@library.tamu.edu}") + private String sugarEmail; @Override public String submit(TicketRequest request) { String results = "Unable to submit ticket to sugar at this time!"; try { - emailService.sendEmail(reportingAddress, getSubject(request), getBody(request)); + emailService.sendEmail(sugarEmail, getSubject(request), getBody(request)); results = "Successfully submitted issue for " + request.getService() + "!"; } catch (MessagingException e) { e.printStackTrace(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 589bc623..b12c1e95 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -27,13 +27,13 @@ spring.jpa.hibernate.ddl-auto: create-drop # logging -logging.level.org.tdl: INFO logging.level.edu.tamu: INFO -logging.level.org.springframework:INFO -logging.level.ro.isdc.wro: INFO +logging.level.org.springframework: INFO logging.file: logs/project-management-service.log +app.sugar.email: helpdesk@library.tamu.edu + app.whitelist: 127.0.0.1 # default delate before first cache update, 5 seconds in milliseconds @@ -48,20 +48,23 @@ app.cache.active-sprints.interval: 900000 # 2 seconds in milliseconds app.cache.remote-projects.delay: 2000 -# 24 hours in milliseconds -app.cache.remote-projects.interval: 86400000 +# 1 hour in milliseconds +app.cache.remote-projects.interval: 3600000 + +# 2 minutes in milliseconds +# the projects stats cache is create from the remote projects cache +# this delay should be greater than the remote projects delay +# and an estimate on how long the remote projects cache takes to populate +app.cache.projects-stats.delay: 120000 +# 1 hour in milliseconds +app.cache.projects-stats.interval: 3600000 ################################################################ # edu.tamu.app.service.SprintsCacheService -# 10 minutes in milliseconds: (10 * 60 * 1000) -app.sprint.cache.interval: 600000 # 10 seconds in milliseconds: (10 * 1000) app.sprint.cache.delay: 10000 -################################################################ - -################################################################ -# edu.tamu.app.service.versioning.VersionOneService -app.vms.versionone.avatar: https://www15.v1host.com/s/18.1.4.12/css/images/no_avatar.png +# 10 minutes in milliseconds: (10 * 60 * 1000) +app.sprint.cache.interval: 600000 ################################################################ ################################################################ diff --git a/src/test/java/edu/tamu/app/ProjectApplicationTest.java b/src/test/java/edu/tamu/app/ProjectApplicationTest.java new file mode 100644 index 00000000..736a342c --- /dev/null +++ b/src/test/java/edu/tamu/app/ProjectApplicationTest.java @@ -0,0 +1,27 @@ +package edu.tamu.app; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@SpringBootTest +@RunWith(SpringRunner.class) +public class ProjectApplicationTest { + + @Test + public void testContextLoads() { + assertTrue("Project application context failed to load!", true); + } + + @Test + public void testProjectApplicationConfigure() { + ProjectApplication application = new ProjectApplication(); + SpringApplicationBuilder builder = new SpringApplicationBuilder(); + application.configure(builder); + } + +} diff --git a/src/test/java/edu/tamu/app/cache/ProjectStatsCacheTest.java b/src/test/java/edu/tamu/app/cache/ProjectStatsCacheTest.java new file mode 100644 index 00000000..92e8529f --- /dev/null +++ b/src/test/java/edu/tamu/app/cache/ProjectStatsCacheTest.java @@ -0,0 +1,60 @@ +package edu.tamu.app.cache; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringRunner; + +import edu.tamu.app.cache.model.ProjectStats; + +@RunWith(SpringRunner.class) +public class ProjectStatsCacheTest { + + @Test + public void testNewProjectStatsCache() { + ProjectsStatsCache cache = new ProjectsStatsCache(); + assertNotNull("New projects stats cache was not created!", cache); + assertNotNull("New projects stats cache projects stats were not created!", cache.get()); + } + + @Test + public void testSetCache() { + ProjectsStatsCache cache = new ProjectsStatsCache(); + assertTrue("Cached projects stats was not empty!", cache.get().isEmpty()); + List projectsStats = new ArrayList(); + projectsStats.add(getMockProjectStats()); + cache.set(projectsStats); + assertFalse("Cached remoteProjects was empty!", cache.get().isEmpty()); + } + + @Test + public void testGetCache() { + ProjectsStatsCache cache = new ProjectsStatsCache(); + List projectsStats = new ArrayList(); + projectsStats.add(getMockProjectStats()); + cache.set(projectsStats); + List remoteProjectsCache = cache.get(); + assertFalse("Cached projects statss was empty!", remoteProjectsCache.isEmpty()); + assertEquals("Cached projects statss had incorrect number of projects statss!", 1, remoteProjectsCache.size()); + + assertEquals("Cached project stats had incorrect id!", "0001", remoteProjectsCache.get(0).getId()); + assertEquals("Cached project stats had incorrect name!", "Sprint 1", remoteProjectsCache.get(0).getName()); + assertEquals("Cached project stats had incorrect number of requests!", 2, remoteProjectsCache.get(0).getRequestCount()); + assertEquals("Cached project stats had incorrect number of issues!", 3, remoteProjectsCache.get(0).getIssueCount()); + assertEquals("Cached project stats had incorrect number of features!", 10, remoteProjectsCache.get(0).getFeatureCount()); + assertEquals("Cached project stats had incorrect number of defects!", 3, remoteProjectsCache.get(0).getDefectCount()); + assertEquals("Cached project stats had incorrect total backlog items!", 13, remoteProjectsCache.get(0).getBacklogItemCount()); + } + + private ProjectStats getMockProjectStats() { + return new ProjectStats("0001", "Sprint 1", 2, 3, 10, 3); + } + +} diff --git a/src/test/java/edu/tamu/app/cache/RemoteProjectsCacheTest.java b/src/test/java/edu/tamu/app/cache/RemoteProjectsCacheTest.java index 40c7d0c6..6ec4b5ed 100644 --- a/src/test/java/edu/tamu/app/cache/RemoteProjectsCacheTest.java +++ b/src/test/java/edu/tamu/app/cache/RemoteProjectsCacheTest.java @@ -22,8 +22,8 @@ public class RemoteProjectsCacheTest { @Test public void testNewRemoteProjectsCache() { RemoteProjectsCache cache = new RemoteProjectsCache(); - assertNotNull("New remote project cache was not created!", cache); - assertNotNull("New remote project cache remoteProjects were not created!", cache.get()); + assertNotNull("New remote projects cache was not created!", cache); + assertNotNull("New remote projects cache remote projects were not created!", cache.get()); } @Test @@ -35,7 +35,7 @@ public void testSetCache() { remoteProjects.add(getMockRemoteProject()); remoteProjectMap.put(1L, remoteProjects); cache.set(remoteProjectMap); - assertFalse("Cached remoteProjects was empty!", cache.get().isEmpty()); + assertFalse("Cached remote projects was empty!", cache.get().isEmpty()); } @Test @@ -51,13 +51,13 @@ public void testGetCache() { assertEquals("Cached remote projects had incorrect number of remote projects!", 1, remoteProjectsCache.size()); assertEquals("Cached remote projects did not have expected remote projects for a given remote project manager!", 1, remoteProjectsCache.get(1L).size()); - assertEquals("Cached remote project had incorrect id!", "0001", remoteProjectsCache.get(1L).get(0).getScopeId()); + assertEquals("Cached remote project had incorrect id!", "0001", remoteProjectsCache.get(1L).get(0).getId()); assertEquals("Cached remote project had incorrect name!", "Sprint 1", remoteProjectsCache.get(1L).get(0).getName()); - assertEquals("Cached remote project had incorrect number of requests!", 2, remoteProjects.get(0).getRequestCount()); - assertEquals("Cached remote project had incorrect number of issues!", 3, remoteProjects.get(0).getIssueCount()); - assertEquals("Cached remote project had incorrect number of stories!", 10, remoteProjects.get(0).getStoryCount()); - assertEquals("Cached remote project had incorrect number of defects!", 3, remoteProjects.get(0).getDefectCount()); - assertEquals("Cached remote project had incorrect total backlog items!", 13, remoteProjects.get(0).getBacklogItemCount()); + assertEquals("Cached remote project had incorrect number of requests!", 2, remoteProjectsCache.get(1L).get(0).getRequestCount()); + assertEquals("Cached remote project had incorrect number of issues!", 3, remoteProjectsCache.get(1L).get(0).getIssueCount()); + assertEquals("Cached remote project had incorrect number of features!", 10, remoteProjectsCache.get(1L).get(0).getFeatureCount()); + assertEquals("Cached remote project had incorrect number of defects!", 3, remoteProjectsCache.get(1L).get(0).getDefectCount()); + assertEquals("Cached remote project had incorrect total backlog items!", 13, remoteProjectsCache.get(1L).get(0).getBacklogItemCount()); } private RemoteProject getMockRemoteProject() { diff --git a/src/test/java/edu/tamu/app/cache/controller/ProjectsStatsCacheControllerTest.java b/src/test/java/edu/tamu/app/cache/controller/ProjectsStatsCacheControllerTest.java new file mode 100644 index 00000000..e8561e97 --- /dev/null +++ b/src/test/java/edu/tamu/app/cache/controller/ProjectsStatsCacheControllerTest.java @@ -0,0 +1,83 @@ +package edu.tamu.app.cache.controller; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.test.context.junit4.SpringRunner; + +import edu.tamu.app.cache.model.ProjectStats; +import edu.tamu.app.cache.service.ProjectsStatsScheduledCacheService; +import edu.tamu.weaver.response.ApiResponse; +import edu.tamu.weaver.response.ApiStatus; + +@RunWith(SpringRunner.class) +public class ProjectsStatsCacheControllerTest { + + @Mock + private ProjectsStatsScheduledCacheService projectsStatsScheduledCacheService; + + @InjectMocks + private ProjectsStatsCacheController projectsStatsCacheController; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + when(projectsStatsScheduledCacheService.get()).thenReturn(getMockProjectsStatsCache()); + } + + @Test + @SuppressWarnings("unchecked") + public void testGet() { + ApiResponse response = projectsStatsCacheController.get(); + assertNotNull("Reponse was null!", response); + assertEquals("Reponse was not successfull!", ApiStatus.SUCCESS, response.getMeta().getStatus()); + + assertNotNull("Reponse payload did not have expected property!", response.getPayload().get("ArrayList")); + assertProjectsStats((List) response.getPayload().get("ArrayList")); + } + + @Test + public void testUpdate() { + ApiResponse response = projectsStatsCacheController.update(); + assertNotNull(response); + assertEquals(ApiStatus.SUCCESS, response.getMeta().getStatus()); + verify(projectsStatsScheduledCacheService, times(1)).update(); + verify(projectsStatsScheduledCacheService, times(1)).broadcast(); + } + + private void assertProjectsStats(List projectsStatsCache) { + assertFalse(projectsStatsCache.isEmpty()); + assertEquals(1, projectsStatsCache.size()); + assertEquals("0001", projectsStatsCache.get(0).getId()); + assertEquals("Sprint 1", projectsStatsCache.get(0).getName()); + assertEquals(2, projectsStatsCache.get(0).getRequestCount()); + assertEquals(3, projectsStatsCache.get(0).getIssueCount()); + assertEquals(10, projectsStatsCache.get(0).getFeatureCount()); + assertEquals(3, projectsStatsCache.get(0).getDefectCount()); + assertEquals(13, projectsStatsCache.get(0).getBacklogItemCount()); + } + + private List getMockProjectsStatsCache() { + List projectsStats = new ArrayList(); + projectsStats.add(getMockProjectStats()); + return projectsStats; + } + + private ProjectStats getMockProjectStats() { + return new ProjectStats("0001", "Sprint 1", 2, 3, 10, 3); + } + +} diff --git a/src/test/java/edu/tamu/app/cache/controller/RemoteProjectsCacheControllerTest.java b/src/test/java/edu/tamu/app/cache/controller/RemoteProjectsCacheControllerTest.java index 8af9c511..55c8265b 100644 --- a/src/test/java/edu/tamu/app/cache/controller/RemoteProjectsCacheControllerTest.java +++ b/src/test/java/edu/tamu/app/cache/controller/RemoteProjectsCacheControllerTest.java @@ -66,11 +66,11 @@ private void assertRemoteProjects(Map> remoteProjectsC List remoteProjects = remoteProjectsCache.get(1L); assertFalse(remoteProjects.isEmpty()); assertEquals(1, remoteProjects.size()); - assertEquals("0001", remoteProjects.get(0).getScopeId()); + assertEquals("0001", remoteProjects.get(0).getId()); assertEquals("Sprint 1", remoteProjects.get(0).getName()); assertEquals(2, remoteProjects.get(0).getRequestCount()); assertEquals(3, remoteProjects.get(0).getIssueCount()); - assertEquals(10, remoteProjects.get(0).getStoryCount()); + assertEquals(10, remoteProjects.get(0).getFeatureCount()); assertEquals(3, remoteProjects.get(0).getDefectCount()); assertEquals(13, remoteProjects.get(0).getBacklogItemCount()); } diff --git a/src/test/java/edu/tamu/app/cache/model/ProjectStatsTest.java b/src/test/java/edu/tamu/app/cache/model/ProjectStatsTest.java new file mode 100644 index 00000000..86cc83f4 --- /dev/null +++ b/src/test/java/edu/tamu/app/cache/model/ProjectStatsTest.java @@ -0,0 +1,24 @@ +package edu.tamu.app.cache.model; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +public class ProjectStatsTest { + + @Test + public void testNewProjectStats() { + ProjectStats projectStats = new ProjectStats("0001", "Sprint 1", 2, 3, 10, 3); + assertEquals("0001", projectStats.getId()); + assertEquals("Sprint 1", projectStats.getName()); + assertEquals(2, projectStats.getRequestCount()); + assertEquals(3, projectStats.getIssueCount()); + assertEquals(10, projectStats.getFeatureCount()); + assertEquals(3, projectStats.getDefectCount()); + assertEquals(13, projectStats.getBacklogItemCount()); + } + +} diff --git a/src/test/java/edu/tamu/app/cache/model/RemoteProjectTest.java b/src/test/java/edu/tamu/app/cache/model/RemoteProjectTest.java index 09f6af38..d64e9a35 100644 --- a/src/test/java/edu/tamu/app/cache/model/RemoteProjectTest.java +++ b/src/test/java/edu/tamu/app/cache/model/RemoteProjectTest.java @@ -12,11 +12,11 @@ public class RemoteProjectTest { @Test public void testNewRemoteProject() { RemoteProject remoteProject = new RemoteProject("0001", "Sprint 1", 2, 3, 10, 3); - assertEquals("0001", remoteProject.getScopeId()); + assertEquals("0001", remoteProject.getId()); assertEquals("Sprint 1", remoteProject.getName()); assertEquals(2, remoteProject.getRequestCount()); assertEquals(3, remoteProject.getIssueCount()); - assertEquals(10, remoteProject.getStoryCount()); + assertEquals(10, remoteProject.getFeatureCount()); assertEquals(3, remoteProject.getDefectCount()); assertEquals(13, remoteProject.getBacklogItemCount()); } diff --git a/src/test/java/edu/tamu/app/cache/service/ProjectsStatsScheduledCacheServiceTest.java b/src/test/java/edu/tamu/app/cache/service/ProjectsStatsScheduledCacheServiceTest.java new file mode 100644 index 00000000..ddd29e45 --- /dev/null +++ b/src/test/java/edu/tamu/app/cache/service/ProjectsStatsScheduledCacheServiceTest.java @@ -0,0 +1,120 @@ +package edu.tamu.app.cache.service; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.test.context.junit4.SpringRunner; + +import com.versionone.apiclient.exceptions.APIException; +import com.versionone.apiclient.exceptions.ConnectionException; +import com.versionone.apiclient.exceptions.OidException; + +import edu.tamu.app.cache.model.ProjectStats; +import edu.tamu.app.cache.model.RemoteProject; +import edu.tamu.app.model.Project; +import edu.tamu.app.model.RemoteProjectManager; +import edu.tamu.app.model.ServiceType; +import edu.tamu.app.model.repo.ProjectRepo; + +@RunWith(SpringRunner.class) +public class ProjectsStatsScheduledCacheServiceTest { + + @Mock + private ProjectRepo projectRepo; + + @Mock + private RemoteProjectsScheduledCacheService remoteProjectsScheduledCacheService; + + @Mock + private SimpMessagingTemplate simpMessagingTemplate; + + @InjectMocks + private ProjectsStatsScheduledCacheService projectsStatsScheduledCacheService; + + @Before + public void setup() throws ConnectionException, APIException, OidException, IOException { + MockitoAnnotations.initMocks(this); + when(projectRepo.findAll()).thenReturn(Arrays.asList(new Project[] { getMockProject() })); + when(remoteProjectsScheduledCacheService.getRemoteProject(any(Long.class), any(String.class))).thenReturn(Optional.of(getMockRemoteProject())); + } + + @Test + public void testSchedule() { + projectsStatsScheduledCacheService.schedule(); + assertProjectsStats(projectsStatsScheduledCacheService.get()); + } + + @Test + public void testUpdate() { + projectsStatsScheduledCacheService.update(); + assertProjectsStats(projectsStatsScheduledCacheService.get()); + } + + @Test + public void testBroadcast() { + projectsStatsScheduledCacheService.broadcast(); + assertTrue(true); + } + + @Test + public void testGet() { + projectsStatsScheduledCacheService.schedule(); + assertProjectsStats(projectsStatsScheduledCacheService.get()); + } + + @Test + public void testSet() { + projectsStatsScheduledCacheService.set(getMockProjectsStatsCache()); + assertProjectsStats(projectsStatsScheduledCacheService.get()); + } + + private List getMockProjectsStatsCache() { + List projectsStatsCache = new ArrayList(); + projectsStatsCache.add(getMockProjectStats()); + return projectsStatsCache; + } + + private ProjectStats getMockProjectStats() { + return new ProjectStats("1", "Test Project", 2, 3, 10, 3); + } + + private RemoteProject getMockRemoteProject() { + return new RemoteProject("0001", "Sprint 1", 2, 3, 10, 3); + } + + private void assertProjectsStats(List projectStatsCache) { + assertFalse(projectStatsCache.isEmpty()); + assertEquals(1, projectStatsCache.size()); + assertEquals("1", projectStatsCache.get(0).getId()); + assertEquals("Test Project", projectStatsCache.get(0).getName()); + assertEquals(2, projectStatsCache.get(0).getRequestCount()); + assertEquals(3, projectStatsCache.get(0).getIssueCount()); + assertEquals(10, projectStatsCache.get(0).getFeatureCount()); + assertEquals(3, projectStatsCache.get(0).getDefectCount()); + assertEquals(13, projectStatsCache.get(0).getBacklogItemCount()); + } + + private Project getMockProject() { + RemoteProjectManager remoteProjectManager = new RemoteProjectManager("Test Remote Project Manager", ServiceType.VERSION_ONE); + Project mockProject = new Project("Test Project", "0001", remoteProjectManager); + mockProject.setId(1L); + return mockProject; + } + +} diff --git a/src/test/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheServiceTest.java b/src/test/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheServiceTest.java index 6ebdafbe..1d1145ce 100644 --- a/src/test/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheServiceTest.java +++ b/src/test/java/edu/tamu/app/cache/service/RemoteProjectsScheduledCacheServiceTest.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.junit.Before; import org.junit.Test; @@ -88,6 +89,14 @@ public void testSet() { assertRemoteProjects(remoteProjectsScheduledCacheService.get()); } + @Test + public void testGetRemoteProject() { + remoteProjectsScheduledCacheService.set(getMockRemoteProjectsCache()); + Optional remoteProject = remoteProjectsScheduledCacheService.getRemoteProject(1L, "0001"); + assertTrue("Coult not find remote project!", remoteProject.isPresent()); + assertRemoteProject(remoteProject.get()); + } + private RemoteProjectManager getMockRemoteProjectManager() { RemoteProjectManager remoteProjectManager = new RemoteProjectManager("Test Remote Project Manager", ServiceType.VERSION_ONE); remoteProjectManager.setId(1L); @@ -112,13 +121,17 @@ private void assertRemoteProjects(Map> remoteProjectsC List remoteProjects = remoteProjectsCache.get(1L); assertFalse(remoteProjects.isEmpty()); assertEquals(1, remoteProjects.size()); - assertEquals("0001", remoteProjects.get(0).getScopeId()); - assertEquals("Sprint 1", remoteProjects.get(0).getName()); - assertEquals(2, remoteProjects.get(0).getRequestCount()); - assertEquals(3, remoteProjects.get(0).getIssueCount()); - assertEquals(10, remoteProjects.get(0).getStoryCount()); - assertEquals(3, remoteProjects.get(0).getDefectCount()); - assertEquals(13, remoteProjects.get(0).getBacklogItemCount()); + assertRemoteProject(remoteProjects.get(0)); + } + + private void assertRemoteProject(RemoteProject remoteProject) { + assertEquals("0001", remoteProject.getId()); + assertEquals("Sprint 1", remoteProject.getName()); + assertEquals(2, remoteProject.getRequestCount()); + assertEquals(3, remoteProject.getIssueCount()); + assertEquals(10, remoteProject.getFeatureCount()); + assertEquals(3, remoteProject.getDefectCount()); + assertEquals(13, remoteProject.getBacklogItemCount()); } } diff --git a/src/test/java/edu/tamu/app/controller/ProjectControllerIntegrationTest.java b/src/test/java/edu/tamu/app/controller/ProjectControllerIntegrationTest.java new file mode 100644 index 00000000..cd0fee32 --- /dev/null +++ b/src/test/java/edu/tamu/app/controller/ProjectControllerIntegrationTest.java @@ -0,0 +1,95 @@ +package edu.tamu.app.controller; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.HashMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import edu.tamu.app.model.Project; +import edu.tamu.app.model.RemoteProjectManager; +import edu.tamu.app.model.ServiceType; +import edu.tamu.app.model.repo.ProjectRepo; +import edu.tamu.app.model.repo.RemoteProjectManagerRepo; + +@SpringBootTest +@AutoConfigureMockMvc +@RunWith(SpringRunner.class) +public class ProjectControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ProjectRepo projectRepo; + + @Autowired + private RemoteProjectManagerRepo remoteProjectManagerRepo; + + @Before + public void setup() { + RemoteProjectManager remoteProjectManager = remoteProjectManagerRepo.create(new RemoteProjectManager("VersionTwo", ServiceType.VERSION_ONE, new HashMap() { + private static final long serialVersionUID = 2020874481642498006L; + { + put("url", "https://localhost:9101/TexasAMLibrary"); + put("username", "username"); + put("password", "password"); + } + })); + Project project = projectRepo.create(new Project("Test")); + project.setScopeId("123456"); + project.setRemoteProjectManager(remoteProjectManager); + project = projectRepo.update(project); + } + + @Test + public void testGetProjects() throws Exception { + // @formatter:off + mockMvc.perform(get("/projects").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("meta.status", equalTo("SUCCESS"))) + .andExpect(jsonPath("payload.ArrayList[0].id", equalTo(1))) + .andExpect(jsonPath("payload.ArrayList[0].name", equalTo("Test"))) + .andExpect(jsonPath("payload.ArrayList[0].scopeId", equalTo("123456"))) + .andExpect(jsonPath("payload.ArrayList[0].remoteProjectManager.id", equalTo(1))) + .andExpect(jsonPath("payload.ArrayList[0].remoteProjectManager.name", equalTo("VersionTwo"))) + .andExpect(jsonPath("payload.ArrayList[0].remoteProjectManager.type", equalTo("VERSION_ONE"))) + .andExpect(jsonPath("payload.ArrayList[0].remoteProjectManager.settings").doesNotExist()); + // @formatter:on + } + + @Test + public void testGetProjectById() throws Exception { + // @formatter:off + mockMvc.perform(get("/projects/2").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("meta.status", equalTo("SUCCESS"))) + .andExpect(jsonPath("payload.Project.id", equalTo(2))) + .andExpect(jsonPath("payload.Project.name", equalTo("Test"))) + .andExpect(jsonPath("payload.Project.scopeId", equalTo("123456"))) + .andExpect(jsonPath("payload.Project.remoteProjectManager.id", equalTo(2))) + .andExpect(jsonPath("payload.Project.remoteProjectManager.name", equalTo("VersionTwo"))) + .andExpect(jsonPath("payload.Project.remoteProjectManager.type", equalTo("VERSION_ONE"))) + .andExpect(jsonPath("payload.Project.remoteProjectManager.settings").doesNotExist()); + // @formatter:on + } + + @After + public void cleanup() { + projectRepo.deleteAll(); + remoteProjectManagerRepo.deleteAll(); + } + +} diff --git a/src/test/java/edu/tamu/app/controller/ProjectControllerTest.java b/src/test/java/edu/tamu/app/controller/ProjectControllerUnitTest.java similarity index 97% rename from src/test/java/edu/tamu/app/controller/ProjectControllerTest.java rename to src/test/java/edu/tamu/app/controller/ProjectControllerUnitTest.java index 22a60607..0c55b8d7 100644 --- a/src/test/java/edu/tamu/app/controller/ProjectControllerTest.java +++ b/src/test/java/edu/tamu/app/controller/ProjectControllerUnitTest.java @@ -19,6 +19,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.springframework.test.context.junit4.SpringRunner; import com.fasterxml.jackson.core.JsonProcessingException; @@ -26,6 +27,7 @@ import com.versionone.apiclient.exceptions.ConnectionException; import com.versionone.apiclient.exceptions.OidException; +import edu.tamu.app.cache.service.ProjectScheduledCache; import edu.tamu.app.model.Project; import edu.tamu.app.model.RemoteProjectManager; import edu.tamu.app.model.ServiceType; @@ -39,7 +41,7 @@ import edu.tamu.weaver.response.ApiResponse; @RunWith(SpringRunner.class) -public class ProjectControllerTest { +public class ProjectControllerUnitTest { private static final String TEST_PROJECT1_NAME = "Test Project 1 Name"; private static final String TEST_PROJECT1_SCOPE = "0001"; @@ -87,6 +89,9 @@ public class ProjectControllerTest { @Mock private RemoteProjectManagerBean managementBean; + @Spy + private List> projectSceduledCaches = new ArrayList>(); + @InjectMocks private ProjectController projectController; diff --git a/src/test/java/edu/tamu/app/service/manager/VersionOneServiceTest.java b/src/test/java/edu/tamu/app/service/manager/VersionOneServiceTest.java index 54e10f96..08869979 100644 --- a/src/test/java/edu/tamu/app/service/manager/VersionOneServiceTest.java +++ b/src/test/java/edu/tamu/app/service/manager/VersionOneServiceTest.java @@ -254,7 +254,7 @@ public void testGetRemoteProjectByScopeId() throws ConnectionException, APIExcep RemoteProject remoteProject = versionOneService.getRemoteProjectByScopeId("1934"); - assertEquals("Remote project has incorrect scope id!", mockRemoteProjects.get(0).getScopeId(), remoteProject.getScopeId()); + assertEquals("Remote project has incorrect scope id!", mockRemoteProjects.get(0).getId(), remoteProject.getId()); assertEquals("Remote project had incorrect name!", mockRemoteProjects.get(0).getName(), remoteProject.getName()); } @@ -832,7 +832,7 @@ private Asset[] getMockRemoteProjectAssets() throws JsonParseException, JsonMapp Oid mockOid = mock(Oid.class); Attribute mockNameAttribute = mock(Attribute.class); when(mockNameAttribute.getValue()).thenReturn(remoteProject.getName()); - when(mockOid.toString()).thenReturn("Scope:" + remoteProject.getScopeId()); + when(mockOid.toString()).thenReturn("Scope:" + remoteProject.getId()); when(mockAsset.getOid()).thenReturn(mockOid); when(mockAsset.getAttribute(any(IAttributeDefinition.class))).thenReturn(mockNameAttribute); mockAssets.add(mockAsset); @@ -843,12 +843,12 @@ private Asset[] getMockRemoteProjectAssets() throws JsonParseException, JsonMapp private Asset[] getMockRemoteProjectAssetByScopeId(String scopeId) throws JsonParseException, JsonMappingException, IOException, APIException { List mockAssets = new ArrayList(); for (RemoteProject remoteProject : mockRemoteProjects) { - if (remoteProject.getScopeId().equals(scopeId)) { + if (remoteProject.getId().equals(scopeId)) { Asset mockAsset = mock(Asset.class); Oid mockOid = mock(Oid.class); Attribute mockNameAttribute = mock(Attribute.class); when(mockNameAttribute.getValue()).thenReturn(remoteProject.getName()); - when(mockOid.toString()).thenReturn("Scope:" + remoteProject.getScopeId()); + when(mockOid.toString()).thenReturn("Scope:" + remoteProject.getId()); when(mockAsset.getOid()).thenReturn(mockOid); when(mockAsset.getAttribute(any(IAttributeDefinition.class))).thenReturn(mockNameAttribute); mockAssets.add(mockAsset); @@ -936,7 +936,7 @@ private void assertRemoteProjects(List remoteProjects) { for (int i = 0; i < mockRemoteProjects.size(); i++) { RemoteProject remoteProject = remoteProjects.get(i); RemoteProject mockRemoteProject = mockRemoteProjects.get(i); - assertEquals("Remote project has incorrect scope id!", mockRemoteProject.getScopeId(), remoteProject.getScopeId()); + assertEquals("Remote project has incorrect scope id!", mockRemoteProject.getId(), remoteProject.getId()); assertEquals("Remote project had incorrect name!", mockRemoteProject.getName(), remoteProject.getName()); } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index b21028e9..1dc8cc90 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -21,13 +21,14 @@ spring.jpa.hibernate.ddl-auto: create-drop # logging -logging.level.org.tdl: INFO logging.level.edu.tamu: INFO -logging.level.org.springframework:INFO -logging.level.ro.isdc.wro: INFO +logging.level.org.springframework: INFO logging.file: logs/project-management-service.log + +app.sugar.email: helpdesk@library.tamu.edu + app.whitelist: 127.0.0.1 # default delate before first cache update, 5 seconds in milliseconds @@ -35,27 +36,30 @@ app.cache.default.delay: 5000 # default interval between cache updates, 1 minute in milliseconds app.cache.default.interval: 60000 -# 10 seconds in milliseconds -app.cache.active-sprints.delay: 10000 +# 2 seconds in milliseconds +app.cache.active-sprints.delay: 2000 # 15 minutes in milliseconds app.cache.active-sprints.interval: 900000 # 2 seconds in milliseconds app.cache.remote-projects.delay: 2000 -# 24 hours in milliseconds -app.cache.remote-projects.interval: 86400000 +# 1 hour in milliseconds +app.cache.remote-projects.interval: 3600000 + +# 2 minutes in milliseconds +# the projects stats cache is create from the remote projects cache +# this delay should be greater than the remote projects delay +# and an estimate on how long the remote projects cache takes to populate +app.cache.projects-stats.delay: 120000 +# 1 hour in milliseconds +app.cache.projects-stats.interval: 3600000 ################################################################ # edu.tamu.app.service.SprintsCacheService -# 10 minutes in milliseconds: (10 * 60 * 1000) -app.sprint.cache.interval: 600000 # 10 seconds in milliseconds: (10 * 1000) app.sprint.cache.delay: 10000 -################################################################ - -################################################################ -# edu.tamu.app.service.versioning.VersionOneService -app.vms.versionone.avatar: https://www15.v1host.com/s/18.1.4.12/css/images/no_avatar.png +# 10 minutes in milliseconds: (10 * 60 * 1000) +app.sprint.cache.interval: 600000 ################################################################ ################################################################ diff --git a/src/test/resources/mock/cache/remote-projects.json b/src/test/resources/mock/cache/remote-projects.json index ca154bb9..6616ca03 100644 --- a/src/test/resources/mock/cache/remote-projects.json +++ b/src/test/resources/mock/cache/remote-projects.json @@ -1,230 +1,230 @@ [ { - "scopeId": "1934", + "id": "1934", "name": "Legacy DSpace" }, { - "scopeId": "3781", + "id": "3781", "name": "Code Management - Maps" }, { - "scopeId": "3783", + "id": "3783", "name": "CORAL - Electronic Resource Management" }, { - "scopeId": "3786", + "id": "3786", "name": "Piper - Automated Ingest" }, { - "scopeId": "3789", + "id": "3789", "name": "Vireo" }, { - "scopeId": "3798", + "id": "3798", "name": "Pelican" }, { - "scopeId": "3816", + "id": "3816", "name": "VIVO" }, { - "scopeId": "3930", + "id": "3930", "name": "Health Based MSL" }, { - "scopeId": "3968", + "id": "3968", "name": "ORCID" }, { - "scopeId": "4114", + "id": "4114", "name": "MIS Reports" }, { - "scopeId": "4182", + "id": "4182", "name": "Collaborative Book Reader" }, { - "scopeId": "4262", + "id": "4262", "name": "DI Internal" }, { - "scopeId": "4391", + "id": "4391", "name": "LibCat" }, { - "scopeId": "4557", + "id": "4557", "name": "Vireo - Undergraduate" }, { - "scopeId": "4731", + "id": "4731", "name": "Key Database" }, { - "scopeId": "4871", + "id": "4871", "name": "MyLibrary Application" }, { - "scopeId": "4889", + "id": "4889", "name": "DSpace" }, { - "scopeId": "4912", + "id": "4912", "name": "iRODS" }, { - "scopeId": "4965", + "id": "4965", "name": "Hours Application" }, { - "scopeId": "5070", + "id": "5070", "name": "MAGPIE" }, { - "scopeId": "5270", + "id": "5270", "name": "Directory App Rewrite" }, { - "scopeId": "5342", + "id": "5342", "name": "Digital Asset Management Ecosystem" }, { - "scopeId": "5523", + "id": "5523", "name": "Applications" }, { - "scopeId": "5524", + "id": "5524", "name": "Legacy Projects" }, { - "scopeId": "5525", + "id": "5525", "name": "Subject Librarians" }, { - "scopeId": "5798", + "id": "5798", "name": "Dev Ops" }, { - "scopeId": "5870", + "id": "5870", "name": "DSpace UI Prototype" }, { - "scopeId": "5910", + "id": "5910", "name": "Library Cascade" }, { - "scopeId": "5962", + "id": "5962", "name": "Instructional Request Manager - Old" }, { - "scopeId": "6004", + "id": "6004", "name": "Weaver (Spring/Angular Frameworks)" }, { - "scopeId": "6029", + "id": "6029", "name": "Sugar CRM UI rewrite" }, { - "scopeId": "6126", + "id": "6126", "name": "Catalog Services" }, { - "scopeId": "6157", + "id": "6157", "name": "Digital Aggieland Portal" }, { - "scopeId": "6368", + "id": "6368", "name": "Instructional Apps" }, { - "scopeId": "6369", + "id": "6369", "name": "Instructional Statistics Manager - Old" }, { - "scopeId": "6373", + "id": "6373", "name": "Instructional Database" }, { - "scopeId": "6486", + "id": "6486", "name": "Automatic Metadata Assignment" }, { - "scopeId": "6487", + "id": "6487", "name": "Semi-Automatic NALT Indexer" }, { - "scopeId": "6620", + "id": "6620", "name": "Development" }, { - "scopeId": "6625", + "id": "6625", "name": "Development Services" }, { - "scopeId": "6626", + "id": "6626", "name": "Application Services" }, { - "scopeId": "6627", + "id": "6627", "name": "VersionOne Service" }, { - "scopeId": "6629", + "id": "6629", "name": "Sugar" }, { - "scopeId": "6664", + "id": "6664", "name": "PHP MicroServices" }, { - "scopeId": "6828", + "id": "6828", "name": "SharePoint" }, { - "scopeId": "7001", + "id": "7001", "name": "SFFRD" }, { - "scopeId": "7036", + "id": "7036", "name": "Instructional Request Manager" }, { - "scopeId": "7037", + "id": "7037", "name": "Instructional Statistics Manager" }, { - "scopeId": "7400", + "id": "7400", "name": "Organization Chart" }, { - "scopeId": "7509", + "id": "7509", "name": "Library Service Status System" }, { - "scopeId": "7516", + "id": "7516", "name": "Fedora" }, { - "scopeId": "7517", + "id": "7517", "name": "Spotlight" }, { - "scopeId": "7529", + "id": "7529", "name": "IIIF Generation Service" }, { - "scopeId": "7797", + "id": "7797", "name": "chronam" }, { - "scopeId": "7869", + "id": "7869", "name": "Cap" }, { - "scopeId": "7934", + "id": "7934", "name": "SAGE" }, { - "scopeId": "7948", + "id": "7948", "name": "Project Management Service" } ] \ No newline at end of file