Skip to content

Commit

Permalink
#3231: Add PendingAssessmentsNotificationWorker
Browse files Browse the repository at this point in the history
  • Loading branch information
gjvoosten committed Nov 16, 2020
1 parent c847701 commit 50d79bf
Show file tree
Hide file tree
Showing 11 changed files with 978 additions and 14 deletions.
22 changes: 21 additions & 1 deletion insertBaseData-mssql.sql
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ INSERT INTO tasks (uuid, shortName, longName, category, createdAt, updatedAt, cu
VALUES
(N'953e0b0b-25e6-44b6-bc77-ef98251d046a', '1.2.A', 'Milestone the First in EF 1.2', 'Milestone', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, N'fe6b6b2f-d2a1-4ce1-9aa7-05361812a4d0', '{ "assessments":[{"questions":{ "question1": { "type": "special_field", "widget": "likertScale", "label": "Test Question 1", "helpText": "Please provide assessment for something important", "levels": [ { "color": "red", "endValue": 2, "label": "test" }, { "color": "#FFBF00", "endValue": 8, "label": "mid" }, { "color": "green", "endValue": 10, "label": "high" } ], "aggregation": { "widget": "likertScale" } }, "question2": { "type": "number", "label": "Test Question 2", "aggregation": { "widget": "numberAggregation" } }, "question3": { "type": "number", "label": "Test Question 3", "aggregation": { "widget": "numberAggregation" } } },"relatedObjectType":"report"}] }'),
(N'9d3da7f4-8266-47af-b518-995f587250c9', '1.2.B', 'Milestone the Second in EF 1.2', 'Milestone', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, N'fe6b6b2f-d2a1-4ce1-9aa7-05361812a4d0', '{ "assessments":[{"questions":{ "frenchFlag": { "type": "special_field", "widget": "likertScale", "label": "French Flag assessment", "helpText": "Please tell us which is the best color in the French flag", "levels": [ { "color": "blue", "endValue": 3.3, "label": "blue" }, { "color": "white", "endValue": 6.6, "label": "white" }, { "color": "red", "endValue": 10, "label": "red" } ] }, "levels": { "type": "enumset", "label": "Achieved levels", "choices": { "lvl1": { "label": "Level 1" }, "lvl2": { "label": "Level 2" }, "lvl3": { "label": "Level 3" } } }, "description": { "type": "special_field", "label": "Detail levels", "widget": "richTextEditor" } },"relatedObjectType":"report"}] }'),
(N'6bbb1be9-4655-48d7-83f2-bc474781544a', '1.2.C', 'Milestone the Third in EF 1.2', 'Milestone', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, N'fe6b6b2f-d2a1-4ce1-9aa7-05361812a4d0', '{ "assessments":[{"questions":{ "question1": { "type": "special_field", "widget": "likertScale", "label": "Monthly assessment Question 1", "helpText": "Please provide assessment for something important", "levels": [ { "color": "red", "endValue": 2, "label": "test" }, { "color": "#FFBF00", "endValue": 8, "label": "mid" }, { "color": "green", "endValue": 10, "label": "high" } ], "aggregation": { "widget": "likertScale" } }, "question2": { "type": "number", "label": "Monthly assessment Question 2", "aggregation": { "widget": "numberAggregation" } }, "question3": { "type": "number", "label": "Monthly assessment Question 3", "aggregation": { "widget": "numberAggregation" } } },"recurrence":"monthly"},{"questions":{ "question1": { "type": "special_field", "widget": "likertScale", "label": "Weekly assessment Question 1", "helpText": "Please provide assessment for something important", "levels": [ { "color": "red", "endValue": 2, "label": "test" }, { "color": "#FFBF00", "endValue": 8, "label": "mid" }, { "color": "green", "endValue": 10, "label": "high" } ], "aggregation": { "widget": "likertScale" } }, "question2": { "type": "number", "label": "Weekly assessment Question 2", "aggregation": { "widget": "numberAggregation" } }, "question3": { "type": "number", "label": "Weekly assessment Question 3", "aggregation": { "widget": "numberAggregation" } } },"recurrence":"weekly"},{"questions":{ "question1": { "type": "special_field", "widget": "likertScale", "label": "Instant assessment Question 1", "helpText": "Please provide assessment for something important", "levels": [ { "color": "red", "endValue": 2, "label": "test" }, { "color": "#FFBF00", "endValue": 8, "label": "mid" }, { "color": "green", "endValue": 10, "label": "high" } ], "aggregation": { "widget": "likertScale" } }, "question2": { "type": "number", "label": "Instant assessment Question 2", "aggregation": { "widget": "numberAggregation" } }, "question3": { "type": "number", "label": "Instant assessment Question 3", "aggregation": { "widget": "numberAggregation" } } },"relatedObjectType":"report"}] }');
(N'6bbb1be9-4655-48d7-83f2-bc474781544a', '1.2.C', 'Milestone the Third in EF 1.2', 'Milestone', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, N'fe6b6b2f-d2a1-4ce1-9aa7-05361812a4d0', '{ "assessments":[{"questions":{ "question1": { "type": "special_field", "widget": "likertScale", "label": "Monthly assessment Question 1", "helpText": "Please provide assessment for something important", "levels": [ { "color": "red", "endValue": 2, "label": "test" }, { "color": "#FFBF00", "endValue": 8, "label": "mid" }, { "color": "green", "endValue": 10, "label": "high" } ], "aggregation": { "widget": "likertScale" } }, "question2": { "type": "number", "label": "Monthly assessment Question 2", "aggregation": { "widget": "numberAggregation" } }, "question3": { "type": "number", "label": "Monthly assessment Question 3", "aggregation": { "widget": "numberAggregation" } } },"recurrence":"quarterly"},{"questions":{ "question1": { "type": "special_field", "widget": "likertScale", "label": "Weekly assessment Question 1", "helpText": "Please provide assessment for something important", "levels": [ { "color": "red", "endValue": 2, "label": "test" }, { "color": "#FFBF00", "endValue": 8, "label": "mid" }, { "color": "green", "endValue": 10, "label": "high" } ], "aggregation": { "widget": "likertScale" } }, "question2": { "type": "number", "label": "Weekly assessment Question 2", "aggregation": { "widget": "numberAggregation" } }, "question3": { "type": "number", "label": "Weekly assessment Question 3", "aggregation": { "widget": "numberAggregation" } } },"recurrence":"daily"},{"questions":{ "question1": { "type": "special_field", "widget": "likertScale", "label": "Instant assessment Question 1", "helpText": "Please provide assessment for something important", "levels": [ { "color": "red", "endValue": 2, "label": "test" }, { "color": "#FFBF00", "endValue": 8, "label": "mid" }, { "color": "green", "endValue": 10, "label": "high" } ], "aggregation": { "widget": "likertScale" } }, "question2": { "type": "number", "label": "Instant assessment Question 2", "aggregation": { "widget": "numberAggregation" } }, "question3": { "type": "number", "label": "Instant assessment Question 3", "aggregation": { "widget": "numberAggregation" } } },"relatedObjectType":"report"}] }');

INSERT INTO tasks (uuid, shortName, longName, category, createdAt, updatedAt, customFieldRef1Uuid)
VALUES
Expand Down Expand Up @@ -952,6 +952,26 @@ INSERT INTO noteRelatedObjects (noteUuid, relatedObjectType, relatedObjectUuid)
FROM tasks t
WHERE t.shortName = '1.2.B';

-- Add periodic assessment for a task
SET @authorUuid = (SELECT uuid FROM people WHERE name = 'ANDERSON, Andrew');
SET @noteUuid = lower(newid());
INSERT INTO notes (uuid, authorUuid, type, text, createdAt, updatedAt)
VALUES (@noteUuid, @authorUuid, 3, '{"status":"GREEN","issues":"<ol><li>one</li><li>two</li><li>three</li></ol>","__recurrence":"monthly","__periodStart":"' + FORMAT(DATEADD(month, DATEDIFF(month, 0, CURRENT_TIMESTAMP), 0), 'yyyy-MM-dd') + '"}', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO noteRelatedObjects (noteUuid, relatedObjectType, relatedObjectUuid)
SELECT @noteUuid, 'tasks', t.uuid
FROM tasks t
WHERE t.shortName = '1.1.A';

-- Add periodic assessment for a person
SET @authorUuid = (SELECT uuid FROM people WHERE name = 'JACKSON, Jack');
SET @noteUuid = lower(newid());
INSERT INTO notes (uuid, authorUuid, type, text, createdAt, updatedAt)
VALUES (@noteUuid, @authorUuid, 3, '{"test3":"3","test2":"3","test1":"3","__recurrence":"quarterly","__periodStart":"' + FORMAT(DATEADD(quarter, DATEDIFF(quarter, 0, CURRENT_TIMESTAMP), 0), 'yyyy-MM-dd') + '"}', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
INSERT INTO noteRelatedObjects (noteUuid, relatedObjectType, relatedObjectUuid)
SELECT @noteUuid, 'people', p.uuid
FROM people p
WHERE p.name = 'ROGWELL, Roger';

-- LEAVE THIS AS LAST STATEMENT
-- Truncate all the dates (on reports etc.) to dates that could have been generated by
-- Java (millisecond precision) rather than by the database itself (microsecond precision)
Expand Down
4 changes: 3 additions & 1 deletion mssql2pg.pl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# Eliminate the weird "[key]" column naming
s/\[key\]/key/g;
# Quote mixed-case column and table names
s/(?<![":])\b([a-z]\w+[A-Z]\w+)/"$1"/g;
s/(?<![":-])\b([a-z]\w+[A-Z]\w+)/"$1"/g;
# Turn a couple very specific 1/0 booleans into true/false booleans
# This one is for "isPrimary" in "reportPeople"
s/(?<=\Q:reportuuid, \E)([10])/$1 ? 'TRUE' : 'FALSE'/ie;
Expand All @@ -32,6 +32,8 @@
# This one is for populating report authorization groups
s/(?<=rp.\"isPrimary\")\s+=\s+([10])/"= " . ($1 ? 'TRUE' : 'FALSE')/e;
# standard date-time math would be so nice...
s/DATEADD\s*\(([^,]*),\s*DATEDIFF\([^,]*, 0, CURRENT_TIMESTAMP\), 0\)/date_trunc('$1', CURRENT_TIMESTAMP)/g;
s/\+ FORMAT\((date_trunc\([^)]*\)), 'yyyy-MM-dd'\) \+/|| to_char($1, 'YYYY-MM-DD') ||/g;
s/DATEADD\s*\(([^,]*),\s*(-?\d+),\s*CURRENT_TIMESTAMP\)/CURRENT_TIMESTAMP + INTERVAL '$2 $1'/g;
s/cast\((\S+) as datetime2\((\d+)\)\)/"date_trunc(" . ($2 eq '3' ? "'milliseconds'" : "'second'") . ", $1)"/ie;
# Function to generate uuid's
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/mil/dds/anet/AnetApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import mil.dds.anet.threads.AnetEmailWorker;
import mil.dds.anet.threads.FutureEngagementWorker;
import mil.dds.anet.threads.MaterializedViewRefreshWorker;
import mil.dds.anet.threads.PendingAssessmentsNotificationWorker;
import mil.dds.anet.threads.ReportApprovalWorker;
import mil.dds.anet.threads.ReportPublicationWorker;
import mil.dds.anet.utils.DaoUtils;
Expand Down Expand Up @@ -238,6 +239,13 @@ public void run(AnetConfiguration configuration, Environment environment)

runAccountDeactivationWorker(configuration, scheduler, engine);

// Check for any missing pending assessments every 6 hours.
// And run once in 25 seconds from boot-up. (give the server time to boot up).
final PendingAssessmentsNotificationWorker pendingAssessmentsNotificationWorker =
new PendingAssessmentsNotificationWorker(configuration);
scheduler.scheduleAtFixedRate(pendingAssessmentsNotificationWorker, 6, 6, TimeUnit.HOURS);
scheduler.schedule(pendingAssessmentsNotificationWorker, 25, TimeUnit.SECONDS);

if (DaoUtils.isPostgresql()) {
// Wait 60 seconds between updates of PostgreSQL materialized views,
// starting 30 seconds after boot-up.
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/mil/dds/anet/beans/JobHistory.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,8 @@ public void setLastRun(Instant lastRun) {
this.lastRun = lastRun;
}

public static Instant getLastRun(JobHistory jobHistory) {
return jobHistory == null ? null : jobHistory.getLastRun();
}

}
16 changes: 14 additions & 2 deletions src/main/java/mil/dds/anet/beans/search/PositionSearchQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ public class PositionSearchQuery extends AbstractSearchQuery<PositionSearchSortB
@GraphQLQuery
@GraphQLInputField
private String authorizationGroupUuid;
@GraphQLQuery
@GraphQLInputField
private Boolean hasCounterparts;

public PositionSearchQuery() {
super(PositionSearchSortBy.NAME);
Expand Down Expand Up @@ -92,10 +95,18 @@ public void setAuthorizationGroupUuid(String authorizationGroupUuid) {
this.authorizationGroupUuid = authorizationGroupUuid;
}

public boolean getHasCounterparts() {
return Boolean.TRUE.equals(hasCounterparts);
}

public void setHasCounterparts(Boolean hasCounterparts) {
this.hasCounterparts = hasCounterparts;
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), matchPersonName, organizationUuid, orgRecurseStrategy,
type, isFilled, locationUuid, authorizationGroupUuid);
type, isFilled, locationUuid, authorizationGroupUuid, hasCounterparts);
}

@Override
Expand All @@ -110,7 +121,8 @@ public boolean equals(Object obj) {
&& Objects.equals(getType(), other.getType())
&& Objects.equals(getIsFilled(), other.getIsFilled())
&& Objects.equals(getLocationUuid(), other.getLocationUuid())
&& Objects.equals(getAuthorizationGroupUuid(), other.getAuthorizationGroupUuid());
&& Objects.equals(getAuthorizationGroupUuid(), other.getAuthorizationGroupUuid())
&& Objects.equals(getHasCounterparts(), other.getHasCounterparts());
}

@Override
Expand Down
10 changes: 0 additions & 10 deletions src/main/java/mil/dds/anet/database/TaskDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,6 @@ public CompletableFuture<List<Organization>> getTaskedOrganizationsForTask(
FkDataLoaderKey.TASK_TASKED_ORGANIZATIONS, taskUuid);
}

@InTransaction
public List<Task> getActiveTasks(boolean topLevel) {
final String sql = String.format(
"/* get%1$sTasks */ SELECT * FROM tasks WHERE status = :active"
+ " AND \"customFieldRef1Uuid\" %2$s NULL",
topLevel ? "TopLevel" : "SubLevel", topLevel ? "IS" : "IS NOT");
return getDbHandle().createQuery(sql).bind("active", DaoUtils.getEnumId(Task.Status.ACTIVE))
.map(new TaskMapper()).list();
}

@Override
public AnetBeanList<Task> search(TaskSearchQuery query) {
return AnetObjectEngine.getInstance().getSearcher().getTaskSearcher().runSearch(query);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package mil.dds.anet.emails;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import mil.dds.anet.AnetObjectEngine;
import mil.dds.anet.beans.Person;
import mil.dds.anet.beans.Position;
import mil.dds.anet.beans.Task;
import mil.dds.anet.utils.IdDataLoaderKey;
import mil.dds.anet.views.UuidFetcher;

public class PendingAssessmentsNotificationEmail implements AnetEmailAction {

private String advisorUuid;
private List<String> positionUuidsToAssess;
private List<String> taskUuidsToAssess;

@Override
public String getTemplateName() {
return "/emails/pendingAssessmentsNotification.ftlh";
}

@Override
public String getSubject(Map<String, Object> context) {
return "ANET assessments due";
}

@Override
public Map<String, Object> buildContext(Map<String, Object> context) {
@SuppressWarnings("unchecked")
final Map<String, Object> dbContext = (Map<String, Object>) context.get("context");

// Load positions and person & organization for each position
final UuidFetcher<Position> positionFetcher = new UuidFetcher<Position>();
final List<CompletableFuture<Position>> positions = positionUuidsToAssess.stream()
.map(uuid -> positionFetcher.load(dbContext, IdDataLoaderKey.POSITIONS, uuid)
.thenCompose(pos -> pos.loadPerson(dbContext).thenCompose(
person -> pos.loadOrganization(dbContext).thenApply(organization -> pos))))
.collect(Collectors.toList());

// Load tasks
final UuidFetcher<Task> taskFetcher = new UuidFetcher<Task>();
final List<CompletableFuture<Task>> tasks = taskUuidsToAssess.stream()
.map(uuid -> taskFetcher.load(dbContext, IdDataLoaderKey.TASKS, uuid))
.collect(Collectors.toList());

// Wait for our futures to complete
final List<CompletableFuture<?>> allFutures = new ArrayList<>();
allFutures.addAll(positions);
allFutures.addAll(tasks);
CompletableFuture.allOf(allFutures.toArray(new CompletableFuture<?>[0])).join();

// Fill email context
context.put("advisor", AnetObjectEngine.getInstance().getPersonDao().getByUuid(advisorUuid));
context.put("positions", positions.stream().map(p -> p.join()).collect(Collectors.toList()));
context.put("tasks", tasks.stream().map(p -> p.join()).collect(Collectors.toList()));
return context;
}

public String getAdvisorUuid() {
return advisorUuid;
}

public void setAdvisorUuid(String advisorUuid) {
this.advisorUuid = advisorUuid;
}

public void setAdvisor(Person advisor) {
this.advisorUuid = advisor.getUuid();
}

public List<String> getPositionUuidsToAssess() {
return positionUuidsToAssess;
}

public void setPositionUuidsToAssess(List<String> positionUuidsToAssess) {
this.positionUuidsToAssess = positionUuidsToAssess;
}

public void setPositionsToAssess(Set<Position> positionsToAssess) {
this.positionUuidsToAssess =
positionsToAssess.stream().map(p -> p.getUuid()).collect(Collectors.toList());
}

public List<String> getTaskUuidsToAssess() {
return taskUuidsToAssess;
}

public void setTaskUuidsToAssess(List<String> taskUuidsToAssess) {
this.taskUuidsToAssess = taskUuidsToAssess;
}

public void setTasksToAssess(Set<Task> tasksToAssess) {
this.taskUuidsToAssess =
tasksToAssess.stream().map(t -> t.getUuid()).collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ protected void buildQuery(PositionSearchQuery query) {
qb.addSqlArg("authorizationGroupUuid", query.getAuthorizationGroupUuid());
}

if (query.getHasCounterparts()) {
qb.addWhereClause("("
+ "positions.uuid IN (SELECT \"positionUuid_a\" FROM \"positionRelationships\""
+ " WHERE \"positionUuid_b\" IS NOT NULL AND deleted = :deleted)"
+ " OR positions.uuid IN (" + "SELECT \"positionUuid_b\" FROM \"positionRelationships\""
+ " WHERE \"positionUuid_a\" IS NOT NULL AND deleted = :deleted))");
qb.addSqlArg("deleted", false);
}

addOrderByClauses(qb, query);
}

Expand Down
Loading

0 comments on commit 50d79bf

Please sign in to comment.