Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to enumerate monitoring team options in config #118

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ src_managed/
project/boot/
project/plugins/project/
project/project/
project/metals.sbt

# Scala-IDE specific
.scala_dependencies
.idea
.idea_modules
.bsp
.metals
.bloop/
project/metals.sbt
.bloop
.vscode
1 change: 1 addition & 0 deletions admin/app/Application.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class PiezoAdminComponents(context: Context) extends BuiltInComponentsFromContex

lazy val schedulerFactory: WorkerSchedulerFactory = new WorkerSchedulerFactory()
lazy val jobFormHelper: JobFormHelper = wire[JobFormHelper]
lazy val monitoringTeams: MonitoringTeams = MonitoringTeams(configuration)

lazy val triggers: Triggers = wire[Triggers]
lazy val jobs: Jobs = wire[Jobs]
Expand Down
7 changes: 4 additions & 3 deletions admin/app/com/lucidchart/piezo/admin/controllers/Jobs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import scala.jdk.CollectionConverters._
import scala.collection.mutable
import scala.Some
import scala.io.Source
import com.lucidchart.piezo.admin.models.MonitoringTeams

trait ImportResult {
val jobKey: Option[JobKey]
Expand All @@ -35,14 +36,14 @@ trait ImportResult {
case class ImportSuccess(val jobKey: Option[JobKey], val errorMessage: String = "", val success: Boolean=true) extends ImportResult
case class ImportFailure(val jobKey: Option[JobKey], val errorMessage: String, val success: Boolean=false) extends ImportResult

class Jobs(schedulerFactory: WorkerSchedulerFactory, jobView: html.job, cc: ControllerComponents) extends AbstractController(cc) with Logging with ErrorLogging with play.api.i18n.I18nSupport {
class Jobs(schedulerFactory: WorkerSchedulerFactory, jobView: html.job, cc: ControllerComponents, monitoringTeams: MonitoringTeams) extends AbstractController(cc) with Logging with ErrorLogging with play.api.i18n.I18nSupport {
val scheduler = logExceptions(schedulerFactory.getScheduler())
val properties = schedulerFactory.props
val jobHistoryModel = logExceptions(new JobHistoryModel(properties))
val triggerMonitoringPriorityModel = logExceptions(new TriggerMonitoringModel(properties))

val jobFormHelper = new JobFormHelper()
val triggerFormHelper = new TriggerFormHelper(scheduler)
val triggerFormHelper = new TriggerFormHelper(scheduler, monitoringTeams)

// Allow up to 1M
private val maxFormSize = 1024 * 1024
Expand Down Expand Up @@ -235,7 +236,7 @@ class Jobs(schedulerFactory: WorkerSchedulerFactory, jobView: html.job, cc: Cont
ImportFailure(Some(jobDetail.getKey), "Trigger Import Error:"+errorMessage)
} else {
val triggers = triggersBinding.flatMap(_.value)
triggers.foreach { case (trigger, monitoringPriority, errorTime, monitoringTeam) =>
triggers.foreach { case TriggerFormValue(trigger, monitoringPriority, errorTime, monitoringTeam) =>
scheduler.scheduleJob(trigger)
triggerMonitoringPriorityModel.setTriggerMonitoringRecord(
trigger.getKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.lucidchart.piezo.admin.controllers

import com.lucidchart.piezo.TriggerMonitoringPriority
import com.lucidchart.piezo.TriggerMonitoringPriority.TriggerMonitoringPriority
import com.lucidchart.piezo.admin.models.MonitoringTeams
import com.lucidchart.piezo.admin.utils.CronHelper
import java.text.ParseException
import org.quartz._
Expand All @@ -11,7 +12,8 @@ import play.api.data.format.Formats.parsing
import play.api.data.format.Formatter
import play.api.data.validation.{Constraint, Constraints, Invalid, Valid, ValidationError}

class TriggerFormHelper(scheduler: Scheduler) extends JobDataHelper {
case class TriggerFormValue(trigger: Trigger, priority: TriggerMonitoringPriority, maxErrorTime: Int, monitoringTeam: Option[String])
class TriggerFormHelper(scheduler: Scheduler, monitoringTeams: MonitoringTeams) extends JobDataHelper {

private def simpleScheduleFormApply(repeatCount: Int, repeatInterval: Int): SimpleScheduleBuilder = {
SimpleScheduleBuilder
Expand Down Expand Up @@ -47,7 +49,7 @@ class TriggerFormHelper(scheduler: Scheduler) extends JobDataHelper {
triggerMonitoringPriority: String,
triggerMaxErrorTime: Int,
triggerMonitoringTeam: Option[String],
): (Trigger, TriggerMonitoringPriority, Int, Option[String]) = {
): TriggerFormValue = {
val newTrigger: Trigger = TriggerBuilder
.newTrigger()
.withIdentity(name, group)
Expand All @@ -59,15 +61,15 @@ class TriggerFormHelper(scheduler: Scheduler) extends JobDataHelper {
.forJob(jobName, jobGroup)
.usingJobData(jobDataMap.getOrElse(new JobDataMap()))
.build()
(
TriggerFormValue(
newTrigger,
TriggerMonitoringPriority.withName(triggerMonitoringPriority),
triggerMaxErrorTime,
triggerMonitoringTeam,
)
}

private def triggerFormUnapply(tp: (Trigger, TriggerMonitoringPriority, Int, Option[String])): Option[
private def triggerFormUnapply(value: TriggerFormValue): Option[
(
String,
String,
Expand All @@ -83,7 +85,7 @@ class TriggerFormHelper(scheduler: Scheduler) extends JobDataHelper {
Option[String],
),
] = {
val trigger = tp._1
val trigger = value.trigger
val (triggerType: String, simple, cron) = trigger match {
case cron: CronTrigger => ("cron", None, Some(cron.getScheduleBuilder))
case simple: SimpleTrigger => ("simple", Some(simple.getScheduleBuilder), None)
Expand All @@ -100,9 +102,9 @@ class TriggerFormHelper(scheduler: Scheduler) extends JobDataHelper {
simple.asInstanceOf[Option[SimpleScheduleBuilder]],
cron.asInstanceOf[Option[CronScheduleBuilder]],
Some(trigger.getJobDataMap),
tp._2.toString,
tp._3,
tp._4,
value.priority.toString,
value.maxErrorTime,
value.monitoringTeam,
),
)
}
Expand Down Expand Up @@ -132,7 +134,7 @@ class TriggerFormHelper(scheduler: Scheduler) extends JobDataHelper {
}
}

def buildTriggerForm: Form[(Trigger, TriggerMonitoringPriority, Int, Option[String])] = Form(
def buildTriggerForm: Form[TriggerFormValue] = Form(
mapping(
"triggerType" -> nonEmptyText(),
"group" -> nonEmptyText(),
Expand All @@ -159,8 +161,14 @@ class TriggerFormHelper(scheduler: Scheduler) extends JobDataHelper {
.verifying(
"Job does not exist",
fields => {
scheduler.checkExists(fields._1.getJobKey)
scheduler.checkExists(fields.trigger.getJobKey)
},
)
.verifying(
"A valid team is required if monitoring is on",
fields => {
!monitoringTeams.teamsDefined || fields.priority == TriggerMonitoringPriority.Off || fields.monitoringTeam.exists(monitoringTeams.value.contains[String])
}
),
)
}
Expand Down
28 changes: 17 additions & 11 deletions admin/app/com/lucidchart/piezo/admin/controllers/Triggers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import scala.util.Try
import play.api.Logging
import play.api.i18n.I18nSupport

class Triggers(schedulerFactory: WorkerSchedulerFactory, cc: ControllerComponents) extends AbstractController(cc) with Logging with ErrorLogging with play.api.i18n.I18nSupport {
class Triggers(schedulerFactory: WorkerSchedulerFactory, cc: ControllerComponents, monitoringTeams: MonitoringTeams)
extends AbstractController(cc) with Logging with ErrorLogging with play.api.i18n.I18nSupport {

val scheduler = logExceptions(schedulerFactory.getScheduler())
val properties = schedulerFactory.props
val triggerHistoryModel = logExceptions(new TriggerHistoryModel(properties))
val triggerMonitoringPriorityModel = logExceptions(new TriggerMonitoringModel(properties))
val triggerFormHelper = new TriggerFormHelper(scheduler)
val triggerFormHelper = new TriggerFormHelper(scheduler, monitoringTeams)

def firesFirst(time: Date)(trigger1: Trigger, trigger2: Trigger): Boolean = {
val time1 = trigger1.getFireTimeAfter(time)
Expand Down Expand Up @@ -160,17 +161,17 @@ class Triggers(schedulerFactory: WorkerSchedulerFactory, cc: ControllerComponent
case "simple" => new DummySimpleTrigger(jobGroup, jobName)
}
val newTriggerForm = triggerFormHelper.buildTriggerForm.fill(
(dummyTrigger, TriggerMonitoringPriority.Low, 300, None)
TriggerFormValue(dummyTrigger, TriggerMonitoringPriority.Low, 300, None)
)
Ok(
com.lucidchart.piezo.admin.views.html.editTrigger(
TriggerHelper.getTriggersByGroup(scheduler),
monitoringTeams.value,
newTriggerForm,
formNewAction,
false,
false
)
(request, implicitly)
)
}
}
Expand All @@ -194,28 +195,30 @@ class Triggers(schedulerFactory: WorkerSchedulerFactory, cc: ControllerComponent
(TriggerMonitoringPriority.Low, 300, None)
}
val editTriggerForm = triggerFormHelper.buildTriggerForm.fill(
(triggerDetail, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam)
TriggerFormValue(triggerDetail, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam)
)
if (isTemplate) {
Ok(
com.lucidchart.piezo.admin.views.html.editTrigger(
TriggerHelper.getTriggersByGroup(scheduler),
monitoringTeams.value,
editTriggerForm,
formNewAction,
false,
isTemplate
)(request, implicitly)
)
)
}
else {
Ok(
com.lucidchart.piezo.admin.views.html.editTrigger(
TriggerHelper.getTriggersByGroup(scheduler),
monitoringTeams.value,
editTriggerForm,
formEditAction(group, name),
true,
isTemplate
)(request, implicitly)
)
)
}
}
Expand All @@ -231,14 +234,15 @@ class Triggers(schedulerFactory: WorkerSchedulerFactory, cc: ControllerComponent
BadRequest(
com.lucidchart.piezo.admin.views.html.editTrigger(
TriggerHelper.getTriggersByGroup(scheduler),
monitoringTeams.value,
formWithErrors,
formEditAction(group, name),
true,
false
)
),
value => {
val (trigger, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam) = value
val TriggerFormValue(trigger, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam) = value
scheduler.rescheduleJob(trigger.getKey(), trigger)
triggerMonitoringPriorityModel.setTriggerMonitoringRecord(
trigger.getKey,
Expand All @@ -258,14 +262,15 @@ class Triggers(schedulerFactory: WorkerSchedulerFactory, cc: ControllerComponent
BadRequest(
com.lucidchart.piezo.admin.views.html.editTrigger(
TriggerHelper.getTriggersByGroup(scheduler),
monitoringTeams.value,
formWithErrors,
formNewAction,
false,
false
)
),
value => {
val (trigger, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam) = value
val TriggerFormValue(trigger, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam) = value
try {
scheduler.scheduleJob(trigger)
triggerMonitoringPriorityModel.setTriggerMonitoringRecord(
Expand All @@ -279,17 +284,18 @@ class Triggers(schedulerFactory: WorkerSchedulerFactory, cc: ControllerComponent
} catch {
case alreadyExists: ObjectAlreadyExistsException =>
val form = triggerFormHelper.buildTriggerForm.fill(
(trigger, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam)
TriggerFormValue(trigger, triggerMonitoringPriority, triggerMaxErrorTime, triggerMonitoringTeam)
)
Ok(
com.lucidchart.piezo.admin.views.html.editTrigger(
TriggerHelper.getTriggersByGroup(scheduler),
monitoringTeams.value,
form,
formNewAction,
false,
false,
errorMessage = Some("Please provide unique group-name pair")
)(request, implicitly)
)
)
}
}
Expand Down
39 changes: 39 additions & 0 deletions admin/app/com/lucidchart/piezo/admin/models/MonitoringTeams.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.lucidchart.piezo.admin.models

import play.api.Configuration
import java.nio.file.Files
import play.api.libs.json.Json
import scala.util.Try
import java.io.File
import java.io.FileInputStream
import play.api.libs.json.JsArray
import play.api.Logging
import scala.util.control.NonFatal
import scala.util.Failure

case class MonitoringTeams(value: Seq[String]) {
def teamsDefined: Boolean = value.nonEmpty
}
object MonitoringTeams extends Logging {
def apply(configuration: Configuration): MonitoringTeams = {
val path = configuration.getOptional[String]("com.lucidchart.piezo.admin.monitoringTeams.path")

val value = path.flatMap { p =>
Try {
Json.parse(new FileInputStream(p))
.as[JsArray]
.value
.map(entry => (entry \ "name").as[String])
.toSeq
}.recoverWith {
case NonFatal(e) =>
logger.error(s"Error reading monitoring teams from $p", e)
Failure(e)
}.toOption
}.getOrElse(Seq.empty)

MonitoringTeams(value)
}

def empty: MonitoringTeams = MonitoringTeams(Seq.empty)
}
25 changes: 16 additions & 9 deletions admin/app/com/lucidchart/piezo/admin/views/editTrigger.scala.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
@(
triggersByGroup: scala.collection.mutable.Buffer[(String, scala.collection.immutable.List[org.quartz.TriggerKey])],
triggerForm: Form[(org.quartz.Trigger, com.lucidchart.piezo.TriggerMonitoringPriority.Value, Int, Option[String])],
monitoringTeams: Seq[String],
triggerForm: Form[com.lucidchart.piezo.admin.controllers.TriggerFormValue],
formAction: play.api.mvc.Call,
existing: Boolean,
isTemplate: Boolean,
errorMessage: Option[String] = None,
scripts: List[String] = List[String]("js/jobData.js", "js/typeAhead.js")
scripts: List[String] = List[String]("js/jobData.js", "js/typeAhead.js", "js/triggerMonitoring.js")
)(
implicit
request: play.api.mvc.Request[AnyContent],
messagesProvider: play.api.i18n.MessagesProvider
messagesProvider: play.api.i18n.MessagesProvider,
)

@import com.lucidchart.piezo.TriggerMonitoringPriority
Expand Down Expand Up @@ -67,12 +68,18 @@ <h4 class="text-danger">@triggerForm.errors.filter(_.key == "").map(_.message).m
}
}
@helper.select(triggerForm("triggerMonitoringPriority"), TriggerMonitoringPriority.values.map(tp => tp.name -> tp.name), Symbol("_label") -> "Monitoring Priority", Symbol("labelClass") -> "col-sm-2 text-right", Symbol("inputDivClass") -> "col-sm-4", Symbol("class") -> "form-control", Symbol("value") -> triggerForm.data.get("triggerMonitoringPriority").getOrElse(TriggerMonitoringPriority.Low), Symbol("placeholder") -> TriggerMonitoringPriority.Low)
@helper.input(triggerForm("triggerMaxErrorTime"), Symbol("_label") -> "Monitoring - Max Seconds Between Successes", Symbol("labelClass") -> "col-sm-2 text-right", Symbol("inputDivClass") -> "col-sm-4", Symbol("placeholder") -> "", Symbol("value") -> triggerForm.data.get("triggerMaxErrorTime").getOrElse(300)) { (id, name, value, args) =>
<input type="number" class="form-control form-inline-control " name="@name" id="@id" @toHtmlArgs(args)>
}
@helper.input(triggerForm("triggerMonitoringTeam"), Symbol("_label") -> "Monitoring team", Symbol("labelClass") -> "col-sm-2 text-right", Symbol("inputDivClass") -> "col-sm-4", Symbol("placeholder") -> "", Symbol("value") -> triggerForm.data.get("triggerMonitoringTeam").getOrElse(None)) { (id, name, value, args) =>
<input type="text" class="form-control form-inline-control" name="@name" id="@id" @toHtmlArgs(args)>
}
<div id="triggerMonitoringDetails">
@helper.input(triggerForm("triggerMaxErrorTime"), Symbol("_label") -> "Monitoring - Max Seconds Between Successes", Symbol("labelClass") -> "col-sm-2 text-right", Symbol("inputDivClass") -> "col-sm-4", Symbol("placeholder") -> "", Symbol("value") -> triggerForm.data.get("triggerMaxErrorTime").getOrElse(300)) { (id, name, value, args) =>
<input type="number" class="form-control form-inline-control " name="@name" id="@id" @toHtmlArgs(args)>
}
@if(monitoringTeams.nonEmpty) {
@helper.select(triggerForm("triggerMonitoringTeam"), monitoringTeams.map(mt => mt -> mt), Symbol("_default") -> "Select team", Symbol("_label") -> "Monitoring team", Symbol("labelClass") -> "col-sm-2 text-right", Symbol("inputDivClass") -> "col-sm-4", Symbol("class") -> "form-control", Symbol("value") -> triggerForm.data.get("triggerMonitoringTeam").getOrElse(""))
} else {
@helper.input(triggerForm("triggerMonitoringTeam"), Symbol("_label") -> "Monitoring team", Symbol("labelClass") -> "col-sm-2 text-right", Symbol("inputDivClass") -> "col-sm-4", Symbol("placeholder") -> "", Symbol("value") -> triggerForm.data.get("triggerMonitoringTeam").getOrElse(None)) { (id, name, value, args) =>
<input type="text" class="form-control form-inline-control" name="@name" id="@id" @toHtmlArgs(args)>
}
}
</div>

@helper.input(triggerForm("description"), Symbol("_label") -> "Description", Symbol("labelClass") -> "col-sm-2 text-right", Symbol("inputDivClass") -> "col-sm-10", Symbol("placeholder") -> "Description", Symbol("value")-> triggerForm.data.get("description").getOrElse("")) { (id, name, value, args) =>
<input type="text" class="form-control form-inline-control " name="@name" id="@id" @toHtmlArgs(args)>
Expand Down
11 changes: 11 additions & 0 deletions admin/conf/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,14 @@ com.lucidchart.piezo.heartbeatFile="/tmp/piezo/workerHeartbeatFile"
com.lucidchart.piezo.admin.production=false
healthCheck.worker.minutesBetween=5
play.application.loader=com.lucidchart.piezo.admin.PiezoAdminApplicationLoader

# Monitoring teams
# ~~~~~
# Path to a JSON file that fills the "Monitoring Team" dropdown on editTrigger
# in the admin UI with a predefined set of team names. File format:
# [
# {"name": "team1"},
# {"name": "team2"}
# ]
# If this is left blank, monitoring team will be a freeform input.
# com.lucidchart.piezo.admin.monitoringTeams.path = "/etc/piezo/teams.json"
15 changes: 15 additions & 0 deletions admin/public/js/triggerMonitoring.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
window.addEventListener('load', () => {
const priorityInput = document.getElementById('triggerMonitoringPriority');
const setMonitoringFieldVisibility = () => {
const priority = priorityInput.value;
const monitoringDetails = document.getElementById('triggerMonitoringDetails');
if (priority == 'Off') {
monitoringDetails.style.display = 'none'; // hide
} else {
monitoringDetails.style.display = 'block'; // show
}
};

priorityInput.addEventListener('change', setMonitoringFieldVisibility);
setMonitoringFieldVisibility();
}, {once: true});
Loading