Skip to content

Commit

Permalink
Adds GUI to master, prepare release 1.8.1
Browse files Browse the repository at this point in the history
  • Loading branch information
bryan-riddle authored and Bryan Riddle committed Mar 26, 2018
1 parent 1d85a18 commit 5a54155
Show file tree
Hide file tree
Showing 14 changed files with 895 additions and 19 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
Support for distributed, highly available (batch) singleton jobs, with job scheduling, locking, supervision and job status persistence.
Implemented with Scala, Akka and Cassandra.

## New in 1.8.1

- added user interface for monitoring and triggering jobs

## New in 1.8.0
- Cross-compilation for scala 2.11 and 2.12
- Play module now uses 2.6.0
Expand Down Expand Up @@ -152,7 +156,7 @@ If your job is implemented as an actor, you can just use the `ActorJob`, as show

You must add the ha-jobs to the dependencies of the build file, e.g. add to `build.sbt`:

libraryDependencies += "de.kaufhof" %% "ha-jobs" % "1.7.2"
libraryDependencies += "de.kaufhof" %% "ha-jobs" % "1.8.1"

It is published to maven central for both scala 2.10 and 2.11.

Expand Down Expand Up @@ -355,15 +359,25 @@ The module `ha-jobs-play` provides a Play! controller that allows to start jobs

To use this you must add the following to the build file:

libraryDependencies += "de.kaufhof" %% "ha-jobs-play" % "1.7.1"
libraryDependencies += "de.kaufhof" %% "ha-jobs-play" % "1.8.1"

In your routes file you have to add these routes (of course you may choose different urls):


GET /jobs @de.kaufhof.hajobs.JobsController.types
POST /jobs/:jobType @de.kaufhof.hajobs.JobsController.run(jobType)
DELETE /jobs/:jobType @de.kaufhof.hajobs.JobsController.cancel(jobType)
GET /jobs/:jobType @de.kaufhof.hajobs.JobsController.list(jobType, limit: Int ?= 20)
GET /jobs/:jobType/latest @de.kaufhof.hajobs.JobsController.latest(jobType)
GET /jobs/:jobType/:jobId @de.kaufhof.hajobs.JobsController.status(jobType, jobId)

# The entry point to the gui
GET /jobsOverview @de.kaufhof.hajobs.OverviewController.index()

# Map static resources from the /public folder to the /assets URL path (used by frontend)
GET /assets/*file controllers.Assets.at(path = "/public", file)

A jobs overview page is available at http://localhost:9000/jobsOverview

Use your preferred dependency injection mechanism to provide the managed `JobsController` to your application. Either by
adding a new module to your application.conf or to your `ApplicationLoader`s load function.
Expand All @@ -372,6 +386,7 @@ adding a new module to your application.conf or to your `ApplicationLoader`s loa
val jobManager = ... // the JobManager
val jobTypes = ... // e.g. JobTypes(ProductImportJobType) in the 1st example
new JobsController(jobManager, jobTypes, de.kaufhof.hajobs.routes.JobsController)
new OverviewController //supplies the single page application gui
```

The `de.kaufhof.hajobs.routes.JobsController` is the reverse router (`ReverseJobsController`) created by Play!
Expand All @@ -380,6 +395,8 @@ on compilation.
Then you can manage your jobs via http, e.g. using the following for a job of `JobType("productimport")`:

```bash
# get a list of all job types
curl http://localhost:9000/jobs
# get a list of all job executions
curl http://localhost:9000/jobs/productimport
# get redirected to the status of the latest job execution
Expand All @@ -388,6 +405,8 @@ curl -L http://localhost:9000/jobs/productimport/latest
curl http://localhost:9000/jobs/productimport/a13037f0-9076-11e4-a8d6-4ff0e8bdfb24
# execute the job
curl -X POST http://localhost:9000/jobs/productimport
# cancel the job execution
curl -X DELETE http://localhost:9000/jobs/productimport
```

## License
Expand Down
12 changes: 7 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val projectVersion = "1.8.0"
val projectVersion = "1.8.1"
val projectScalaVersion = "2.12.2"

scalaVersion := projectScalaVersion
Expand Down Expand Up @@ -64,10 +64,11 @@ val publishSettings = Seq(
</developers>
)

val playVersion = "2.6.0"
val akkaVersion = "2.5.3"
val playVersion = "2.6.11"
val akkaVersion = "2.5.9"
val scalatest = "org.scalatest" %% "scalatest" % "3.0.3" % "test"
val mockito = "org.mockito" % "mockito-core" % "2.8.47" % "test"

val playTest = "com.typesafe.play" %% "play-test" % playVersion % "test"

lazy val core = project.in(file("ha-jobs-core"))
Expand All @@ -79,7 +80,7 @@ lazy val core = project.in(file("ha-jobs-core"))
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/",
libraryDependencies ++= Seq(
"com.datastax.cassandra" % "cassandra-driver-core" % "3.3.0",
"com.typesafe.play" %% "play-json" % playVersion exclude("com.typesafe.play", "play_" + scalaVersion.value.substring(0, 4)),
"com.typesafe.play" %% "play-json" % "2.6.9" exclude("com.typesafe.play", "play_" + scalaVersion.value.substring(0, 4)),
"joda-time" % "joda-time" % "2.9.9",
"org.slf4j" % "slf4j-api" % "1.7.25",
"org.quartz-scheduler" % "quartz" % "2.3.0",
Expand All @@ -93,6 +94,7 @@ lazy val core = project.in(file("ha-jobs-core"))
)

lazy val play = project.in(file("ha-jobs-play"))
.enablePlugins(SbtWeb)
.dependsOn(core)
.settings(
name := "ha-jobs-play",
Expand All @@ -105,7 +107,7 @@ lazy val play = project.in(file("ha-jobs-play"))
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/",
libraryDependencies ++= Seq(
"com.typesafe.play" %% "play" % playVersion,
"com.typesafe.play" %% "play-json" % playVersion,
"com.typesafe.play" %% "play-json" % "2.6.9",
playTest,
scalatest,
mockito
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ class JobManager(managedJobs: => Jobs,
.newTrigger
.withIdentity(s"$jobName-trigger")
.withSchedule(CronScheduleBuilder
// this will throw an exception if the expression is incorrect
.cronSchedule(cronExpression)
// this will throw an exception if the expression is incorrect
.cronSchedule(cronExpression)
.inTimeZone(schedulesTimeZone)
).build()

Expand Down Expand Up @@ -247,6 +247,10 @@ class JobManager(managedJobs: => Jobs,

def jobStatus(jobType: JobType, jobId: UUID): Future[Option[JobStatus]] = jobStatusRepo.get(jobType, jobId)

def getAllJobTypes(): Future[List[JobType]] = jobStatusRepo.getAllActiveTypes()

def getCronExpression(jobType: JobType): Option[String] = getJob(jobType).cronExpression

private[hajobs] def retriggerCounts: Map[JobType, Int] = managedJobs.map { case (jobType, job) =>
jobType -> job.retriggerCount
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class JobStatusRepository(session: Session,
limitByJobType: JobType => Int = JobStatusRepository.defaultLimitByJobType)
(implicit ec: ExecutionContext): Future[Map[JobType, List[JobStatus]]] = {
def getAllMetadata(jobType: JobType): Future[(JobType, List[JobStatus])] = {
import scala.collection.JavaConversions._
import scala.collection.JavaConverters._
implicit def dateTimeOrdering: Ordering[DateTime] = Ordering.fromLessThan(_ isAfter _)


Expand All @@ -139,7 +139,7 @@ class JobStatusRepository(session: Session,
selectMetadata <- Future(selectStmt)
metadata <- session.executeAsync(selectMetadata).map(res => {
val jobStatusList: List[JobStatus] =
res.all.toList.flatMap(row => rowToStatus(row, isMeta = true)).sortBy(_.jobStatusTs)
res.all.asScala.toList.flatMap(row => rowToStatus(row, isMeta = true)).sortBy(_.jobStatusTs)
jobType -> jobStatusList
})
} yield {
Expand All @@ -155,7 +155,7 @@ class JobStatusRepository(session: Session,
*/
def getJobHistory(jobType: JobType, jobId: UUID, withQuorum: Boolean = false)
(implicit ec: ExecutionContext): Future[List[JobStatus]] = {
import scala.collection.JavaConversions._
import scala.collection.JavaConverters._

val selectStmt = select().all()
.from(DataTable)
Expand All @@ -167,14 +167,14 @@ class JobStatusRepository(session: Session,
}

session.executeAsync(selectStmt).map(res =>
res.all.toList.flatMap( row =>
res.all.asScala.toList.flatMap( row =>
rowToStatus(row, isMeta = false)
))
}

def list(jobType: JobType, limit: Int = 20, withQuorum: Boolean = false)
(implicit ec: ExecutionContext): Future[List[JobStatus]] = {
import scala.collection.JavaConversions._
import scala.collection.JavaConverters._
val selectStmt = select().all()
.from(MetaTable)
.where(QueryBuilder.eq(JobTypeColumn, jobType.name))
Expand All @@ -185,7 +185,7 @@ class JobStatusRepository(session: Session,
}

session.executeAsync(selectStmt).map(res =>
res.all().toList.flatMap( result =>
res.all().asScala.toList.flatMap( result =>
rowToStatus(result, isMeta = true)
))
}
Expand All @@ -209,6 +209,32 @@ class JobStatusRepository(session: Session,
))
}

/**
* Returns all JobTypes for which jobs have been run at least once
* (i.e. which have an entry in the MetaTable)
*/
def getAllActiveTypes() (implicit ec: ExecutionContext): Future[List[JobType]]= {
import scala.collection.JavaConverters._
// SELECT DISTINCT jobTypeColumn FROM MetaTable
val selectStmt = select(JobTypeColumn).distinct().from(MetaTable)

session.executeAsync(selectStmt).map(res =>
res.all().asScala.toList.flatMap( result =>
rowToJobType(result)
))
}

private def rowToJobType(row: Row): Option[JobType] = {
def table = "job_status_meta"

val jobTypeName = row.getString(JobTypeColumn)
jobTypes(jobTypeName) match {
case Some(jobType) => Option(jobType)
case None => logger.error(s"Could not find JobType for name: $jobTypeName(table $table)")
None
}
}

private def rowToStatus(row: Row, isMeta: Boolean): Option[JobStatus] = {

def table = if(isMeta) "job_status_meta" else "job_status_data"
Expand Down
47 changes: 47 additions & 0 deletions ha-jobs-play/src/main/public/css/overview.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
.my-app .popover{
max-width: inherit;
}

.vis-item.vis-selected{
background: #bb5bd4;
}

.my-app .tab-pane .table{
margin-bottom: 0;
}

.my-app .vis-time-axis .vis-text{
color: white;
}

pre {
color: #ffffff;
padding: 5px;
margin: 5px;
background-color: inherit;
}

.object.syntax{
color: white;
}

.string {
color: #1ede6c;
white-space: initial;
}

.number {
color: #bb5bd4;
}

.boolean {
color: #ca1559;
}

.null {
color: #7c5000;
}

.key {
color: #1dd0d3;
}
Loading

0 comments on commit 5a54155

Please sign in to comment.