": [
+ {
+ "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
\nAll the key/value information will go right into the Notes field.
\n
\nWe 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