Skip to content

Commit eb17e05

Browse files
authored
Merge pull request #73 from Simon-Initiative/add-info-to-email
Add info to email
2 parents e4e39f5 + 284096b commit eb17e05

5 files changed

Lines changed: 157 additions & 46 deletions

File tree

service.example.envs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@ svn_projects=https://example.edu/svn/content/editor/projects/dev-local/
3030
#dataset_db=jdbc:mysql://example.edu:3306?useTimezone=true&serverTimezone=UTC&user=xxxxxxx&password=xxxxxxxxx
3131
dataset_db=jdbc:mysql://example.edu:3306?user=xxxxxxx&password=xxxxxxxx
3232

33+
slack_alert_hook=none
34+
3335
# Java runtime options
3436
JAVA_OPTS=-Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -Dxml.catalog.ignoreMissing=true -Dxml.catalog.files=/oli/dtd/catalog.xml -Dxml.catalog.verbosity=1 -Dxml.catalog.prefer=public -Dxml.catalog.staticCatalog=yes -Dxml.catalog.allowPI=yes -Dxml.catalog.className=com.sun.org.apache.xml.internal.resolver.Resolver

src/main/java/edu/cmu/oli/content/AppUtils.java

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import edu.cmu.oli.content.models.persistance.entities.ContentPackage;
77
import edu.cmu.oli.content.models.persistance.entities.ErrorLevel;
88
import edu.cmu.oli.content.models.persistance.entities.Resource;
9+
import edu.cmu.oli.content.resource.builders.Xml2Json;
910
import org.apache.tika.Tika;
1011
import org.apache.xml.resolver.tools.CatalogResolver;
1112
import org.jdom2.Attribute;
@@ -22,22 +23,19 @@
2223
import javax.json.JsonValue;
2324
import javax.naming.InitialContext;
2425
import javax.naming.NamingException;
25-
26+
import javax.ws.rs.client.ClientBuilder;
27+
import javax.ws.rs.client.Entity;
28+
import javax.ws.rs.client.WebTarget;
29+
import javax.ws.rs.core.MediaType;
30+
import javax.ws.rs.core.Response;
2631
import java.io.*;
2732
import java.nio.file.Files;
2833
import java.nio.file.Path;
2934
import java.security.MessageDigest;
3035
import java.security.NoSuchAlgorithmException;
3136
import java.security.SecureRandom;
32-
import java.util.ArrayList;
33-
import java.util.HashMap;
34-
import java.util.List;
35-
import java.util.Map;
36-
import java.util.Optional;
37-
import java.util.Random;
38-
import java.util.UUID;
37+
import java.util.*;
3938
import java.util.stream.Collectors;
40-
import edu.cmu.oli.content.resource.builders.Xml2Json;
4139

4240
/**
4341
* @author Raphael Gachuhi
@@ -92,7 +90,7 @@ public static String inputStreamToString(InputStream input) throws IOException {
9290
}
9391

9492
public static void addToPackageError(ContentPackage contentPackage, String message, String source,
95-
ErrorLevel level) {
93+
ErrorLevel level) {
9694
if (contentPackage.getErrors() == null) {
9795
JsonObject errors = new JsonObject();
9896
errors.addProperty("contentPackageErrors", contentPackage.getId() + "_" + contentPackage.getVersion());
@@ -129,13 +127,13 @@ public static void addToResourceError(Resource resource, String message, String
129127

130128
private static boolean isQuestionWithParts(Element element) {
131129
if (element.getName().equals("multiple_choice")
132-
|| element.getName().equals("ordering")
133-
|| element.getName().equals("short_answer")
134-
|| element.getName().equals("essay")
135-
|| element.getName().equals("numeric")
136-
|| element.getName().equals("text")
137-
|| element.getName().equals("fill_in_the_blank")
138-
|| element.getName().equals("question")) {
130+
|| element.getName().equals("ordering")
131+
|| element.getName().equals("short_answer")
132+
|| element.getName().equals("essay")
133+
|| element.getName().equals("numeric")
134+
|| element.getName().equals("text")
135+
|| element.getName().equals("fill_in_the_blank")
136+
|| element.getName().equals("question")) {
139137
return true;
140138
}
141139

@@ -169,8 +167,8 @@ private static String getQuestionLabel(Element question) {
169167

170168
switch (question.getName()) {
171169
case "multiple_choice":
172-
if (question.getAttribute("select") != null
173-
&& question.getAttribute("select").getValue().equals("single")) {
170+
if (question.getAttribute("select") != null
171+
&& question.getAttribute("select").getValue().equals("single")) {
174172
return "Multiple Choice";
175173
}
176174
return "Check All That Apply";
@@ -219,16 +217,16 @@ public static int countPoolQuestions(Element element) {
219217
if (element.getName().equals("pool")) {
220218
for (Element c : element.getChildren()) {
221219
switch (c.getName()) {
222-
case "multiple_choice":
223-
case "ordering":
224-
case "short_answer":
225-
case "essay":
226-
case "numeric":
227-
case "text":
228-
case "fill_in_the_blank":
229-
case "question":
230-
count = count + 1;
231-
default:
220+
case "multiple_choice":
221+
case "ordering":
222+
case "short_answer":
223+
case "essay":
224+
case "numeric":
225+
case "text":
226+
case "fill_in_the_blank":
227+
case "question":
228+
count = count + 1;
229+
default:
232230
}
233231
}
234232
}
@@ -368,29 +366,26 @@ public enum EmbedActivityType {
368366
UNKNOWN("UNKNOWN");
369367

370368
private String type;
371-
369+
372370
EmbedActivityType(String type) {
373371
this.type = type;
374372
}
375-
373+
376374
public String getAsString() {
377375
return type;
378376
}
379-
377+
380378
private static final Map<String, EmbedActivityType> reverseLookup = new HashMap<>();
381-
379+
382380
// Populate the reverse lookup table on loading time
383-
static
384-
{
385-
for(EmbedActivityType activityType : EmbedActivityType.values())
386-
{
381+
static {
382+
for (EmbedActivityType activityType : EmbedActivityType.values()) {
387383
reverseLookup.put(activityType.getAsString(), activityType);
388384
}
389385
}
390-
386+
391387
//This method can be used for reverse lookup purpose
392-
public static EmbedActivityType fromString(String type)
393-
{
388+
public static EmbedActivityType fromString(String type) {
394389
return reverseLookup.get(type);
395390
}
396391
}
@@ -399,7 +394,7 @@ public static EmbedActivityType inferEmbedActivityType(JsonObject embedActivity)
399394
// use activity_type property or infer type based on content
400395
if (embedActivity.has("@activity_type")) {
401396
// use activity_type attribute to determine type
402-
switch(embedActivity.get("@activity_type").getAsString().toLowerCase()) {
397+
switch (embedActivity.get("@activity_type").getAsString().toLowerCase()) {
403398
case "repl":
404399
return EmbedActivityType.REPL;
405400
default:
@@ -424,7 +419,7 @@ public static EmbedActivityType inferEmbedActivityType(JsonObject embedActivity)
424419
String source = itemObj.get("source").getAsJsonObject().get("#text").getAsString();
425420
if (source.endsWith("activity.js") || source.endsWith("repl.js")) {
426421
flags = flags | 0b1;
427-
422+
428423
if (flags == REPL_FLAGS) {
429424
return EmbedActivityType.REPL;
430425
}
@@ -467,7 +462,20 @@ public static EmbedActivityType inferEmbedActivityType(JsonObject embedActivity)
467462
}
468463

469464
}
470-
465+
471466
return EmbedActivityType.UNKNOWN;
472467
}
468+
469+
public static Response.Status sendSlackAlert(JsonObject message) {
470+
String slackHook = System.getenv().get("slack_alert_hook");
471+
if (slackHook == null || slackHook.isEmpty() || slackHook.equalsIgnoreCase("none")) {
472+
return Response.Status.FORBIDDEN;
473+
}
474+
WebTarget target = ClientBuilder.newClient().target(slackHook);
475+
Response response = target.request(MediaType.APPLICATION_JSON)
476+
.post(Entity.json(AppUtils.gsonBuilder().create().toJson(message)));
477+
log.info("response code " + response.getStatusInfo() + " code " + response.getStatus());
478+
479+
return Response.Status.fromStatusCode(response.getStatus());
480+
}
473481
}

src/main/java/edu/cmu/oli/content/controllers/DeployControllerImpl.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
import edu.cmu.oli.content.security.AppSecurityController;
4040
import edu.cmu.oli.content.security.Secure;
4141
import edu.cmu.oli.content.security.UserInfo;
42+
import org.jdom2.Document;
43+
import org.jdom2.Element;
44+
import org.jdom2.JDOMException;
45+
import org.jdom2.filter.Filters;
46+
import org.jdom2.input.SAXBuilder;
47+
import org.jdom2.input.sax.XMLReaders;
48+
import org.jdom2.xpath.XPathExpression;
49+
import org.jdom2.xpath.XPathFactory;
4250
import org.slf4j.Logger;
4351

4452
import javax.ejb.Stateless;
@@ -55,8 +63,14 @@
5563
import javax.ws.rs.client.WebTarget;
5664
import javax.ws.rs.core.MediaType;
5765
import javax.ws.rs.core.Response;
66+
import java.io.File;
67+
import java.io.IOException;
68+
import java.io.StringReader;
69+
import java.nio.charset.StandardCharsets;
70+
import java.nio.file.Files;
5871
import java.nio.file.Paths;
5972
import java.util.*;
73+
import java.util.stream.Collectors;
6074

6175
@Stateless
6276
public class DeployControllerImpl implements DeployController {
@@ -247,7 +261,9 @@ public void sendRequestDeployEmail(AppSecurityContext session, ContentPackage pk
247261
sb.append(packageDetails(pkg));
248262

249263
// Send email to authors only
250-
doSendEmail(pkg, authorEmailsj, subject, sb.toString(), Optional.empty());
264+
if(authorEmailsj.size() > 0) {
265+
doSendEmail(pkg, authorEmailsj, subject, sb.toString(), Optional.empty());
266+
}
251267

252268
if (svnLocation != null) {
253269
sb.append("SVN Location: " + svnLocation + "\n\n");
@@ -332,6 +348,35 @@ private String packageDetails(ContentPackage contentPackage) {
332348
sb.append("Package Title: " + contentPackage.getTitle() + "\n");
333349
sb.append("Description: " + contentPackage.getDescription() + "\n");
334350
sb.append("Last Updated: " + contentPackage.getDateUpdated() + "\n\n");
351+
352+
CriteriaBuilder cb = em.getCriteriaBuilder();
353+
CriteriaQuery<Resource> criteria = cb.createQuery(Resource.class);
354+
Root<Resource> resourceRoot = criteria.from(Resource.class);
355+
criteria.select(resourceRoot)
356+
.where(cb.and(cb.equal(resourceRoot.get("contentPackage").get("guid"), contentPackage.getGuid()),
357+
resourceRoot.get("type").in("x-oli-organization")));
358+
List<Resource> organizationList = em.createQuery(criteria).getResultList();
359+
List<Optional<String>> orgRelatedMessages = organizationList.stream().map(o -> {
360+
String path = contentPackage.getSourceLocation() + File.separator + o.getFileNode().getPathFrom();
361+
SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
362+
builder.setExpandEntities(false);
363+
Document document = null;
364+
String message = null;
365+
try {
366+
document = builder.build(new StringReader(new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8)));
367+
} catch (JDOMException | IOException e) {
368+
log.error(e.getLocalizedMessage(), e);
369+
return Optional.ofNullable(message);
370+
}
371+
XPathExpression<Element> xexpression = XPathFactory.instance().compile("//module", Filters.element());
372+
if (xexpression.evaluate(document).isEmpty()) {
373+
message = "Warning: An organization (id=" + o.getId() +") has no modules\n" +
374+
"Organizations without at least one module have learning dashboard limitations in OLI\n\n";
375+
}
376+
return Optional.ofNullable(message);
377+
378+
}).filter(Optional::isPresent).collect(Collectors.toList());
379+
orgRelatedMessages.forEach(e->sb.append(e.get()));
335380
return sb.toString();
336381
}
337382

@@ -347,6 +392,8 @@ private void doSendEmail(ContentPackage contentPackage, JsonArray toEmails, Stri
347392
payload.add("attachments", attachments.get());
348393
}
349394

395+
log.info("email payload " + new Gson().toJson(payload));
396+
350397
WebTarget target = ClientBuilder.newClient().target(configuration.get().getEmailServer());
351398
Response response = target.request(MediaType.APPLICATION_JSON)
352399
.post(Entity.json(AppUtils.gsonBuilder().create().toJson(payload)));

src/main/java/edu/cmu/oli/content/controllers/SVNSyncController.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ private Map<String, List<File>> doUpdateSvnRepo(File base, String packageGuid) {
274274

275275
} catch (SVNException e) {
276276
log.error("SVN Add Entry Error: ", e);
277+
JsonObject alert = new JsonObject();
278+
alert.addProperty("text", System.getenv().get("SERVER_URL")
279+
+ " SVN Add Entry Error: working copy name:- " + base.getName() +
280+
" \nerror message " + e.getLocalizedMessage() +" \nsvn url:- " +svnUrl.get(0));
281+
AppUtils.sendSlackAlert(alert);
277282
}
278283

279284
Map<String, Path> repoFiles = new HashMap<>();
@@ -334,11 +339,23 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx
334339

335340
} catch (SVNException ex) {
336341
log.error("SVN Update Error: ", ex);
342+
JsonObject alert = new JsonObject();
343+
alert.addProperty("text", System.getenv().get("SERVER_URL")
344+
+ " SVN Update Error: working copy name:- " + base.getName() +
345+
" \nerror message " + ex.getLocalizedMessage() +" \nsvn url:- " +svnUrl.get(0));
346+
AppUtils.sendSlackAlert(alert);
337347
}
348+
338349
try {
339350
svnCommit(base, svnManager);
340351
} catch (SVNException ex) {
341352
log.error("SVN Commit Error: ", ex);
353+
JsonObject alert = new JsonObject();
354+
alert.addProperty("text", System.getenv().get("SERVER_URL")
355+
+" SVN Commit Error: working copy name:- " + base.getName() +
356+
" \nerror message " + ex.getLocalizedMessage() +" \nsvn url:- " +svnUrl.get(0));
357+
358+
AppUtils.sendSlackAlert(alert);
342359
}
343360

344361
log.info("Done Committing changes to svn " + base.getName());

src/test/java/edu/cmu/oli/content/boundary/PlainTest.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
import java.text.DateFormat;
1111
import java.text.ParseException;
1212
import java.text.SimpleDateFormat;
13+
import java.time.ZoneId;
1314
import java.util.*;
15+
import java.util.concurrent.TimeUnit;
1416

1517
import static org.junit.Assert.assertTrue;
1618

@@ -21,7 +23,6 @@ public class PlainTest {
2123

2224
@Test
2325
public void testNothing() throws JDOMException, IOException {
24-
2526
String val = "After sorting ints: ";
2627
MessageDigest messageDigest = null;
2728
try {
@@ -33,7 +34,12 @@ public void testNothing() throws JDOMException, IOException {
3334
messageDigest.update(val.getBytes());
3435
String encryptedString = convertByteArrayToHexString(messageDigest.digest());
3536
System.out.println("After sorting ints: " + encryptedString);
36-
assertTrue(true);
37+
38+
System.out.println("Time zones in GMT:");
39+
List<String> gmt = getTimeZoneList(OffsetBase.GMT);
40+
for (String timeZone : gmt) {
41+
System.out.println(timeZone);
42+
}
3743
}
3844

3945
private static String convertByteArrayToHexString(byte[] arrayBytes) {
@@ -45,4 +51,35 @@ private static String convertByteArrayToHexString(byte[] arrayBytes) {
4551
return stringBuffer.toString();
4652
}
4753

54+
public enum OffsetBase {
55+
GMT, UTC
56+
}
57+
58+
public List<String> getTimeZoneList(OffsetBase base) {
59+
String[] availableZoneIds = TimeZone.getAvailableIDs();
60+
List<String> result = new ArrayList<>(availableZoneIds.length);
61+
62+
for (String zoneId : availableZoneIds) {
63+
TimeZone curTimeZone = TimeZone.getTimeZone(zoneId);
64+
65+
String offset = calculateOffset(curTimeZone.getRawOffset());
66+
67+
result.add(String.format("(%s%s) %s", base, offset, zoneId ));
68+
}
69+
70+
Collections.sort(result);
71+
72+
return result;
73+
}
74+
75+
private String calculateOffset(int rawOffset) {
76+
if (rawOffset == 0) {
77+
return "+00:00";
78+
}
79+
long hours = TimeUnit.MILLISECONDS.toHours(rawOffset);
80+
long minutes = TimeUnit.MILLISECONDS.toMinutes(rawOffset);
81+
minutes = Math.abs(minutes - TimeUnit.HOURS.toMinutes(hours));
82+
83+
return String.format("%+03d:%02d", hours, Math.abs(minutes));
84+
}
4885
}

0 commit comments

Comments
 (0)