Skip to content

Commit

Permalink
Merge pull request #430 from NationalSecurityAgency/t#424/notify_appr…
Browse files Browse the repository at this point in the history
…ove_reject

#424 - notify users when self reported skills are approved or denied
  • Loading branch information
sudo-may authored Mar 22, 2021
2 parents bc89981 + dc8fb04 commit 158bb76
Show file tree
Hide file tree
Showing 5 changed files with 469 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Copyright 2020 SkillTree
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package skills.notify.builders

import groovy.json.JsonSlurper
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.thymeleaf.context.Context
import org.thymeleaf.spring5.SpringTemplateEngine
import skills.storage.model.Notification

@Component
class SkillApprovalResponseNotificationBuilder implements NotificationEmailBuilder{

JsonSlurper jsonSlurper = new JsonSlurper()

@Autowired
SpringTemplateEngine thymeleafTemplateEngine;

@Override
String getId() {
return Notification.Type.SkillApprovalResponse.toString()
}

@Override
Res build(Notification notification, Formatting formatting) {
def parsed = jsonSlurper.parseText(notification.encodedParams)
Context context = buildThymeleafContext(parsed, formatting)
String htmlBody = thymeleafTemplateEngine.process("skill_approval_response.html", context)
String plainText = buildPlainText(parsed, formatting)
return new Res(
subject: "SkillTree Points ${parsed.approved ? 'Approved' : 'Denied'}",
html: htmlBody,
plainText: plainText
)
}

private Context buildThymeleafContext(parsed, Formatting formatting) {
Context templateContext = new Context()
templateContext.setVariable("approver", parsed.approver)
templateContext.setVariable("approved", parsed.approved)
templateContext.setVariable("skillName", parsed.skillName)
templateContext.setVariable("skillId", parsed.skillId)
templateContext.setVariable("projectName", parsed.projectName)
templateContext.setVariable("projectId", parsed.projectId)
templateContext.setVariable("rejectionMsg", parsed.rejectionMsg ?: '')
templateContext.setVariable("publicUrl", parsed.publicUrl)
templateContext.setVariable("htmlHeader", formatting.htmlHeader)
templateContext.setVariable("htmlFooter", formatting.htmlFooter)

return templateContext
}

private String buildPlainText(parsed, Formatting formatting) {
String message
if (parsed.approved) {
message = "Congratulations! Your request for the ${parsed.skillName} skill in the ${parsed.projectName} project has been approved."
} else {
message = "Your request for the ${parsed.skillName} skill in the ${parsed.projectName} project has been denied."
}
String pt = "${message}" +
"\n Project: ${parsed.projectName}" +
"\n Skill: ${parsed.skillName}" +
"\n Approver: ${parsed.approver}" +
"\n ${parsed.approved ? '' : "Message: ${parsed.rejectionMsg ?: ''}\n"}" +
"\n" +
"\nAlways yours," +
"\nSkillTree Bot"

if (formatting.plaintextHeader) {
pt = "${formatting.plaintextHeader}\n${pt}"
}
if (formatting.plaintextFooter) {
pt = "${pt}\n${formatting.plaintextFooter}"
}

return pt
}

}
81 changes: 76 additions & 5 deletions service/src/main/java/skills/services/SkillApprovalService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,24 @@ import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import skills.auth.UserInfoService
import skills.controller.exceptions.ErrorCode
import skills.controller.exceptions.SkillException
import skills.controller.result.model.LabelCountItem
import skills.controller.result.model.SettingsResult
import skills.controller.result.model.SkillApprovalResult
import skills.controller.result.model.TableResult
import skills.notify.EmailNotifier
import skills.notify.Notifier
import skills.services.events.SkillEventResult
import skills.services.events.SkillEventsService
import skills.storage.model.SkillApproval
import skills.storage.model.SkillDef
import skills.storage.model.SkillDefWithExtra
import skills.services.settings.Settings
import skills.services.settings.SettingsService
import skills.storage.model.*
import skills.storage.repos.ProjDefRepo
import skills.storage.repos.SkillApprovalRepo
import skills.storage.repos.SkillDefRepo
import skills.storage.repos.UserAttrsRepo

import java.util.stream.Stream

Expand All @@ -47,6 +53,21 @@ class SkillApprovalService {
@Autowired
SkillDefRepo skillDefRepo

@Autowired
ProjDefRepo projDefRepo

@Autowired
UserAttrsRepo userAttrsRepo

@Autowired
SettingsService settingsService

@Autowired
UserInfoService userInfoService

@Autowired
EmailNotifier notifier

TableResult getApprovals(String projectId, PageRequest pageRequest) {
List<SkillApprovalRepo.SimpleSkillApproval> approvalsFromDB = skillApprovalRepo.findToApproveByProjectIdAndNotRejected(projectId, pageRequest)
List<SkillApprovalResult> approvals = approvalsFromDB.collect { SkillApprovalRepo.SimpleSkillApproval simpleSkillApproval ->
Expand Down Expand Up @@ -92,18 +113,26 @@ class SkillApprovalService {
}

skillApprovalRepo.delete(it)

// send email
sentNotifications(it, skillDef, true)
}
}

void reject(String projectId, List<Integer> approvalRequestIds, String rejectionMsg) {
List<SkillApproval> toApprove = skillApprovalRepo.findAllById(approvalRequestIds)
toApprove.each {
List<SkillApproval> toReject = skillApprovalRepo.findAllById(approvalRequestIds)
toReject.each {
validateProjId(it, projectId)

it.rejectionMsg = rejectionMsg
it.rejectedOn = new Date()

skillApprovalRepo.save(it)

// send email
Optional<SkillDef> optional = skillDefRepo.findById(it.skillRefId)
SkillDef skillDef = optional.get()
sentNotifications(it, skillDef, false, rejectionMsg)
}
}

Expand Down Expand Up @@ -152,4 +181,46 @@ class SkillApprovalService {
}
}

private void sentNotifications(SkillApproval skillApproval, SkillDef skillDefinition, boolean approved, String rejectionMsg=null) {
String publicUrl = getPublicUrl()
if(!publicUrl) {
return
}

UserAttrs userAttrs = userAttrsRepo.findByUserId(skillApproval.userId)
if (!userAttrs.email) {
return
}

ProjDef projDef = projDefRepo.findByProjectId(skillDefinition.projectId)
Notifier.NotificationRequest request = new Notifier.NotificationRequest(
userIds: [skillApproval.userId],
type: Notification.Type.SkillApprovalResponse.toString(),
keyValParams: [
approver : userInfoService.currentUser.usernameForDisplay,
approved : approved,
skillName : skillDefinition.name,
skillId : skillDefinition.skillId,
projectName : projDef.name,
projectId : skillDefinition.projectId,
rejectionMsg : rejectionMsg,
publicUrl : publicUrl,
],
)
notifier.sendNotification(request)
}

private String getPublicUrl() {
SettingsResult publicUrlSetting = settingsService.getGlobalSetting(Settings.GLOBAL_PUBLIC_URL.settingName)
if (!publicUrlSetting) {
log.warn("Skill approval notifications are disabled since global setting [${Settings.GLOBAL_PUBLIC_URL}] is NOT set")
return null
}

String publicUrl = publicUrlSetting.value
if (!publicUrl.endsWith("/")){
publicUrl += "/"
}
return publicUrl
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import javax.persistence.*
class Notification {

static enum Type {
SkillApprovalRequested,
SkillApprovalRequested, SkillApprovalResponse
}

static class KeyValParam {
Expand Down
50 changes: 50 additions & 0 deletions service/src/main/resources/templates/skill_approval_response.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!--
Copyright 2020 SkillTree
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.thymeleaf.org http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>
.label {
color: #525252;
}
</style>
</head>
<body class="overall-container">
<div th:remove="tag" th:utext="${htmlHeader}" th:if= "${htmlHeader != null}">[[${htmlHeader}]]</div>
<h1>SkillTree Points <span th:text="${approved} ? 'Approved!' : 'Denied'"></span></h1>
<p th:if="${approved}">Congratulations! Your request for the <b>[[${skillName}]]</b> skill in the <b>[[${projectName}]]</b> project has been approved!</p>
<p th:if="${!approved}">Your request for the <b>[[${skillName}]]</b> skill in the <b>[[${projectName}]]</b> project has been denied.</p>

<ul>
<li><span class="label">Project</span>: [[${projectName}]]</li>
<li><span class="label">Skill</span>: [[${skillName}]]</li>
<li><span class="label">Approver</span>: [[${approver}]]</li>
<li th:if="${!approved}"><span class="label">Message</span>: [[${rejectionMsg}]]</li>
</ul>

<p>You can view your progress for the <a th:href="@{${publicUrl}+'progress-and-rankings/projects/'+${projectId}}">[[${projectName}]]</a> project in the SkillTree dashboard.</p>

<p>
Always yours, <br/> -SkillTree Bot
</p>

<div th:remove="tag" th:utext="${htmlFooter}" th:if= "${htmlFooter != null}">[[${htmlFooter}]]</div>
</body>
</html>
Loading

0 comments on commit 158bb76

Please sign in to comment.