All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lucidchart.piezo.admin.controllers.Jobs.scala Maven / Gradle / Ivy

The newest version!
package com.lucidchart.piezo.admin.controllers

import com.lucidchart.piezo.admin.utils.JobUtils
import com.lucidchart.piezo.admin.utils.JobDetailHelper._
import com.lucidchart.piezo.admin.views._
import com.lucidchart.piezo.{JobHistoryModel, TriggerHistoryModel, TriggerMonitoringModel, WorkerSchedulerFactory}
import org.quartz._
import org.quartz.impl.matchers.GroupMatcher
import play.api._
import play.api.libs.functional.syntax._
import play.api.libs.json._
import play.api.mvc._
import scala.jdk.CollectionConverters._
import scala.collection.mutable
import scala.Some
import scala.io.Source

trait ImportResult {
  val jobKey: Option[JobKey]
  val errorMessage: String
  val success: Boolean
  def toJson = {
    Json.obj(
      "success" -> success,
      "errorMessage" -> errorMessage
    ) ++ jobKey.map { jk =>
        Json.obj(
          "jobName" -> jk.getName,
          "jobGroup" -> jk.getGroup
        )
    }.getOrElse(Json.obj())
  }
}

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 {
  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)

  // Allow up to 1M
  private val maxFormSize = 1024 * 1024

  def getJobsByGroup(): mutable.Buffer[(String, List[JobKey])] = {
    val jobsByGroup =
      for (groupName <- scheduler.getJobGroupNames().asScala) yield {
        val jobs: List[JobKey] = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName)).asScala.toList
        val sortedJobs: List[JobKey] = jobs.sortBy(jobKey => jobKey.getName())
        (groupName, sortedJobs)
      }
    jobsByGroup.sortBy(groupList => groupList._1)
  }

  def getIndex = Action { implicit request =>
    val allJobs: List[JobKey] = getJobsByGroup().flatMap(_._2).toList
    val jobHistories = allJobs.flatMap({ job =>
      jobHistoryModel.getJob(job).headOption
    }).sortWith(_.start after _.start)
    val triggeredJobs: List[JobKey] = TriggerHelper.getTriggersByGroup(scheduler).flatMap { case (group, triggerKeys) =>
      triggerKeys.map(triggerKey => scheduler.getTrigger(triggerKey).getJobKey)
    }.toList
    val untriggeredJobs: List[JobKey] = allJobs.filterNot(x => triggeredJobs.contains(x))
    Ok(com.lucidchart.piezo.admin.views.html.jobs(getJobsByGroup(), None, Some(jobHistories), untriggeredJobs, scheduler.getMetaData)(request))
  }

  def getJob(group: String, name: String) = Action { implicit request =>
    val jobKey = new JobKey(name, group)
    val jobExists = scheduler.checkExists(jobKey)
    if (!jobExists) {

      request.accepts(HTML)
      val errorMsg = Some("Job " + group + " " + name + " not found")
      NotFound(jobView(getJobsByGroup(), None, None, None, errorMsg)(request))
    } else {
      try {
        val jobDetail: Option[JobDetail] = Some(scheduler.getJobDetail(jobKey))

        val history = {
          try {
            Some(jobHistoryModel.getJob(jobKey))
          } catch {
            case e: Exception => {
              logger.error("Failed to get job history")
              None
            }
          }
        }

        val triggers = scheduler.getTriggersOfJob(jobKey).asScala.toList
        val (resumableTriggers, pausableTriggers) = triggers.filter(_.isInstanceOf[CronTrigger]).map(_.getKey()).partition{ triggerKey =>
          scheduler.getTriggerState(triggerKey) == Trigger.TriggerState.PAUSED
        }

        Ok(jobView(getJobsByGroup(), jobDetail, history, Some(triggers), None, pausableTriggers, resumableTriggers)(request))
      } catch {
        case e: Exception => {
          val errorMsg = "Exception caught getting job " + group + " " + name + ". -- " + e.getLocalizedMessage()
          logger.error(errorMsg, e)
          InternalServerError(jobView(getJobsByGroup(), None, None, None, Some(errorMsg))(request))
        }
      }
    }
  }

  def deleteJob(group: String, name: String) = Action { implicit request =>
    val jobKey = new JobKey(name, group)
    if (!scheduler.checkExists(jobKey)) {
      val errorMsg = Some("Job %s $s not found".format(group, name))
      NotFound(jobView(mutable.Buffer(), None, None, None, errorMsg)(request))
    } else {
      try {
        scheduler.deleteJob(jobKey)
        Ok(jobView(getJobsByGroup(), None, None, None)(request))
      } catch {
        case e: Exception => {
          val errorMsg = "Exception caught deleting job %s %s. -- %s".format(group, name, e.getLocalizedMessage())
          logger.error(errorMsg, e)
          InternalServerError(jobView(mutable.Buffer(), None, None, None, Some(errorMsg))(request))
        }
      }
    }
  }

  def getJobDetail(group: String, name: String) = Action { implicit request =>
    if(request.accepts(JSON)) {
      val jobKey = new JobKey(name, group)
      if (scheduler.checkExists(jobKey)) {
        val jobDetail = scheduler.getJobDetail(jobKey)
        val triggers = scheduler.getTriggersOfJob(jobKey).asScala.toList
        Ok(Json.toJson(Seq(jobDetail))(jobDetailSeqWrites(triggers, triggerMonitoringPriorityModel)))
      } else {
        NotFound(Json.obj("message" -> "specified job doesn't exist"))
      }
    } else {
      UnsupportedMediaType
    }
  }

  def getJobsDetail = Action { implicit request =>
    if (request.accepts(JSON)) {
      Ok(
        Json.toJson(
          scheduler.getJobGroupNames().asScala.toList.flatMap { group =>
            val jobKeys = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group)).asScala
            jobKeys.map { jk =>
              val triggers = scheduler.getTriggersOfJob(jk).asScala.toList
              Json.toJson(scheduler.getJobDetail(jk))(jobDetailWrites(triggers, triggerMonitoringPriorityModel))
            }
          }
        )
      )
    } else {
      UnsupportedMediaType
    }
  }

  val submitNewMessage = "Create"
  val formNewAction = routes.Jobs.postJob
  val submitEditMessage = "Save"
  def formEditAction(group: String, name: String): Call = routes.Jobs.putJob(group, name)

  def getNewJobForm(templateGroup: Option[String] = None, templateName: Option[String] = None) = Action { implicit request =>
    //if (request.queryString.contains())
    templateGroup match {
      case Some(group) => getEditJob(group, templateName.get, true)
      case None =>
        val newJobForm = jobFormHelper.buildJobForm
        Ok(com.lucidchart.piezo.admin.views.html.editJob(getJobsByGroup(), newJobForm, submitNewMessage, formNewAction, false)(request, implicitly))
    }

  }

  def getEditJob(group: String, name: String, isTemplate: Boolean)(implicit request: Request[AnyContent]) = {
    val jobKey = new JobKey(name, group)

    if (scheduler.checkExists(jobKey)) {
      val jobDetail = scheduler.getJobDetail(jobKey)
      val editJobForm = jobFormHelper.buildJobForm.fill(jobDetail)
      if (isTemplate) Ok(com.lucidchart.piezo.admin.views.html.editJob(getJobsByGroup(), editJobForm, submitNewMessage, formNewAction, false)(request, implicitly))
      else Ok(com.lucidchart.piezo.admin.views.html.editJob(getJobsByGroup(), editJobForm, submitEditMessage, formEditAction(group, name), true)(request, implicitly))
    } else {
      val errorMsg = Some("Job %s %s not found".format(group, name))
      NotFound(com.lucidchart.piezo.admin.views.html.trigger(mutable.Buffer(), None, None, errorMsg)(request))
    }
  }

  def getEditJobAction(group: String, name: String) = Action { implicit request => getEditJob(group, name, false) }

  def putJob(group: String, name: String) = Action { implicit request =>
    jobFormHelper.buildJobForm.bindFromRequest().fold(
      formWithErrors =>
        BadRequest(html.editJob(getJobsByGroup(), formWithErrors, submitNewMessage, formNewAction, false)),
      value => {
        val jobDetail = JobUtils.cleanup(value)
        scheduler.addJob(jobDetail, true)
        Redirect(routes.Jobs.getJob(value.getKey.getGroup(), value.getKey.getName()))
          .flashing("message" -> "Successfully edited job.", "class" -> "")
      }
    )
  }

  def parseJson(json: JsValue): List[ImportResult] = {
    def formErrorStr(formErrors: Seq[play.api.data.FormError]): String = {
      formErrors.map { e =>
        (if(e.key.nonEmpty) s"${e.key}:" else "") + e.message
      }.mkString(",")
    }

    json.as[List[JsObject]].map { jsObject =>
      jobFormHelper.buildJobForm.bind(jsObject, maxFormSize).fold ( e => {
        val jobKey = for {
          name <- (jsObject \ "name").validate[String]
          group <- (jsObject \ "group").validate[String]
        } yield {
          new JobKey(name, group)
        }
        val errorMessage = formErrorStr(e.errors)
        logger.error(errorMessage)
        ImportFailure(jobKey.asOpt, "Job Import Error:" + errorMessage)
      }, value => {
        try {
          val jobDetail = JobUtils.cleanup(value)
          scheduler.addJob(jobDetail, false)
          val triggersOpt = (jsObject \ "triggers").asOpt[List[JsObject]]
          val triggersBinding = triggersOpt.map(_.map(triggerFormHelper.buildTriggerForm.bind(_, maxFormSize))).getOrElse(Nil)
          if(triggersBinding.exists(b => b.hasErrors || b.hasGlobalErrors)) {
            val errorMessage = formErrorStr(triggersBinding.filter(_.hasErrors).flatMap(_.errors))
            logger.error(errorMessage)
            ImportFailure(Some(jobDetail.getKey), "Trigger Import Error:"+errorMessage)
          } else {
            val triggers = triggersBinding.flatMap(_.value)
            triggers.foreach { case (trigger, monitoringPriority, errorTime, monitoringTeam) =>
              scheduler.scheduleJob(trigger)
              triggerMonitoringPriorityModel.setTriggerMonitoringRecord(
                trigger.getKey,
                monitoringPriority,
                errorTime,
                monitoringTeam,
              )
            }
            ImportSuccess(Some(jobDetail.getKey))
          }
        } catch {
          case _: ObjectAlreadyExistsException =>
            logger.error(s"Failed to add Job ${value.getKey.getGroup} - ${value.getKey.getGroup} because it already exists")
            ImportFailure(Some(value.getKey), "The Job Already Exists")
        }
      })
    }
  }

  def postJobs = Action { implicit request: Request[AnyContent] =>
    val jsonOpt = request.body.asJson.orElse {
      request.body.asMultipartFormData.flatMap { d =>
        d.file("file").map { f =>
          Json.parse(Source.fromFile(f.ref.path.toFile()).getLines().mkString)
        }
      }
    }
    jsonOpt.map { json =>
      val results = parseJson(json)
      if(results.forall(_.success == true)) {
        Created(Json.obj(
          "count" -> results.size,
          "failures" -> Json.arr()
        ))
      } else {
        Created(Json.obj(
          "count" -> results.size,
          "failures" -> results.filter(_.success == false).map(_.toJson)
        ))
      }
    }.getOrElse {
      BadRequest
    }
  }

  def postJob = Action { implicit request =>
    jobFormHelper.buildJobForm.bindFromRequest().fold(
      formWithErrors =>
        BadRequest(com.lucidchart.piezo.admin.views.html.editJob(getJobsByGroup(), formWithErrors, submitNewMessage, formNewAction, false)),
      value => {
        try {
          val jobDetail = JobUtils.cleanup(value)
          scheduler.addJob(jobDetail, false)
          Redirect(routes.Jobs.getJob(value.getKey.getGroup(), value.getKey.getName()))
            .flashing("message" -> "Successfully added job.", "class" -> "")
        } catch {
          case alreadyExists: ObjectAlreadyExistsException =>
            val form = jobFormHelper.buildJobForm.fill(value)
            Ok(com.lucidchart.piezo.admin.views.html.editJob(getJobsByGroup(),
              form,
              submitNewMessage,
              formNewAction,
              false,
              errorMessage = Some("Please provide unique group-name pair")
            )(request, implicitly))
        }
      }
    )
  }

  def jobGroupTypeAhead(sofar: String) = Action { implicit request =>
    val groups = scheduler.getJobGroupNames().asScala.toList

    Ok(Json.obj("groups" -> groups.filter{ group =>
      group.toLowerCase.contains(sofar.toLowerCase)
    }))
  }

  def jobNameTypeAhead(group: String, sofar: String) = Action { implicit request =>
    val jobs = scheduler.getJobKeys(GroupMatcher.jobGroupEquals(group)).asScala.toSet

    Ok(Json.obj("jobs" -> jobs.filter(_.getName.toLowerCase.contains(sofar.toLowerCase)).map(_.getName)))
  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy