Skip to content

Commit

Permalink
Graphdoc support!
Browse files Browse the repository at this point in the history
  • Loading branch information
kyleu committed Apr 15, 2018
1 parent c564567 commit 54db553
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 51 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ logs
conf/generated.keystore
project/project/target
project/target
doc/src/main/graphdoc
target
tmp
.history
Expand Down
14 changes: 11 additions & 3 deletions app/controllers/admin/system/SandboxController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import controllers.BaseController
import models.Application
import models.sandbox.SandboxTask
import services.ServiceRegistry
import services.graphql.GraphQLService

import scala.concurrent.Future
import scala.concurrent.duration._

import util.JsonSerializers._

@javax.inject.Singleton
class SandboxController @javax.inject.Inject() (override val app: Application, services: ServiceRegistry) extends BaseController("sandbox") {
class SandboxController @javax.inject.Inject() (
override val app: Application, services: ServiceRegistry, graphQLService: GraphQLService
) extends BaseController("sandbox") {
import app.contexts.webContext

implicit val timeout: Timeout = Timeout(10.seconds)
Expand All @@ -21,8 +26,11 @@ class SandboxController @javax.inject.Inject() (override val app: Application, s

def run(key: String, arg: Option[String]) = withSession("sandbox." + key, admin = true) { implicit request => implicit td =>
val sandbox = SandboxTask.withNameInsensitive(key)
sandbox.run(app, services, arg).map { result =>
Ok(views.html.admin.sandbox.sandboxRun(request.identity, result))
sandbox.run(SandboxTask.Config(app, services, graphQLService, arg)).map { result =>
render {
case Accepts.Html() => Ok(views.html.admin.sandbox.sandboxRun(request.identity, result))
case Accepts.Json() => Ok(result.asJson)
}
}
}
}
3 changes: 2 additions & 1 deletion app/models/graphql/GraphQLContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package models.graphql
import models.Application
import models.auth.Credentials
import services.ServiceRegistry
import services.graphql.GraphQLService
import util.tracing.TraceData

final case class GraphQLContext(app: Application, services: ServiceRegistry, creds: Credentials, trace: TraceData)
final case class GraphQLContext(app: Application, svc: GraphQLService, services: ServiceRegistry, creds: Credentials, trace: TraceData)
64 changes: 64 additions & 0 deletions app/models/sandbox/GraphdocLogic.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package models.sandbox

import better.files._
import models.Application
import models.auth.Credentials
import services.graphql.GraphQLService
import util.FutureUtils.serviceContext
import util.tracing.TraceData

import scala.sys.process._
import scala.util.control.NonFatal

object GraphdocLogic {
private[this] val d = "./doc/src/main/graphdoc".toFile

def generate(creds: Credentials, app: Application, graphQLService: GraphQLService, argument: Option[String])(implicit td: TraceData) = {
checkInstalled()
if (argument.contains("force") && !d.exists) {
d.createDirectories()
}
if (d.isDirectory) {
run(creds, app, graphQLService)
} else {
throw new IllegalStateException(s"""Directory [${d.path}] doe not exist, pass "force" as an argument to create.""")
}
}

private[this] val queryFilename = "IntrospectionQuery.graphql"
private[this] val schemaFilename = "schema.json"

private[this] lazy val introspectionQuery = Option(getClass.getClassLoader.getResourceAsStream(queryFilename)) match {
case Some(q) => scala.io.Source.fromInputStream(q).getLines().mkString("\n")
case None => throw new IllegalStateException(s"Cannot read [$queryFilename].")
}

private[this] def writeIntrospectionResult(creds: Credentials, app: Application, graphQLService: GraphQLService)(implicit td: TraceData) = try {
val f = d / schemaFilename
if (!f.exists) {
f.createFile()
}
graphQLService.executeQuery(app, introspectionQuery, None, None, creds, debug = false).map(_.spaces2).map { json =>
f.overwrite(json)
}
} catch {
case NonFatal(x) => throw new IllegalStateException(s"Cannot run graphdoc. Is it installed? (${x.getMessage})", x)
}

private[this] def run(creds: Credentials, app: Application, graphQLService: GraphQLService)(implicit td: TraceData) = {
writeIntrospectionResult(creds, app, graphQLService).map { _ =>
val args = Seq("graphdoc", "--force", "-s", (d / schemaFilename).pathAsString, "-o", d.pathAsString)
try {
args.!!
} catch {
case NonFatal(x) => throw new IllegalStateException(s"Cannot run [$args]. (${x.getMessage})", x)
}
}
}

private[this] def checkInstalled() = try {
Seq("graphdoc", "--help").!!
} catch {
case NonFatal(x) => throw new IllegalStateException(s"Cannot run graphdoc. Is it installed? (${x.getMessage})", x)
}
}
2 changes: 1 addition & 1 deletion app/models/sandbox/SandboxSchema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object SandboxSchema {
fieldType = sandboxResultType,
description = Some("Allows calling of sandbox tests."),
arguments = sandboxTaskArg :: sandboxArgumentArg :: Nil,
resolve = c => c.arg(sandboxTaskArg).run(c.ctx.app, c.ctx.services, c.arg(sandboxArgumentArg))(c.ctx.trace)
resolve = c => c.arg(sandboxTaskArg).run(SandboxTask.Config(c.ctx.app, c.ctx.services, c.ctx.svc, c.arg(sandboxArgumentArg)))(c.ctx.trace)
)
)
}
42 changes: 29 additions & 13 deletions app/models/sandbox/SandboxTask.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,83 @@ package models.sandbox

import enumeratum.{CirceEnum, Enum, EnumEntry}
import models.Application
import models.auth.Credentials
import services.ServiceRegistry
import services.database.BackupRestore
import services.graphql.GraphQLService
import util.FutureUtils.defaultContext
import util.Logging
import util.tracing.TraceData
import util.JsonSerializers._

import scala.concurrent.Future

sealed abstract class SandboxTask(val id: String, val name: String, val description: String) extends EnumEntry with Logging {
def run(app: Application, services: ServiceRegistry, arg: Option[String])(implicit trace: TraceData): Future[SandboxTask.Result] = {
app.tracing.trace(id + ".sandbox") { sandboxTrace =>
def run(cfg: SandboxTask.Config)(implicit trace: TraceData): Future[SandboxTask.Result] = {
cfg.app.tracing.trace(id + ".sandbox") { sandboxTrace =>
log.info(s"Running sandbox task [$id]...")
val startMs = System.currentTimeMillis
val result = call(app, services, arg)(sandboxTrace).map { r =>
val res = SandboxTask.Result(this, arg, "OK", r, (System.currentTimeMillis - startMs).toInt)
val result = call(cfg)(sandboxTrace).map { r =>
val res = SandboxTask.Result(this, cfg.argument, "OK", r, (System.currentTimeMillis - startMs).toInt)
log.info(s"Completed sandbox task [$id] with status [${res.status}] in [${res.elapsed}ms].")
res
}
result
}
}
def call(app: Application, services: ServiceRegistry, argument: Option[String])(implicit trace: TraceData): Future[String]
def call(cfg: SandboxTask.Config)(implicit trace: TraceData): Future[String]
override def toString = id
}

object SandboxTask extends Enum[SandboxTask] with CirceEnum[SandboxTask] {
final case class Config(app: Application, services: ServiceRegistry, graphQLService: GraphQLService, argument: Option[String])

final case class Result(task: SandboxTask, arg: Option[String], status: String = "OK", result: String, elapsed: Int)

object Result {
implicit val jsonEncoder: Encoder[Result] = deriveEncoder
implicit val jsonDecoder: Decoder[Result] = deriveDecoder
}

case object Testbed extends SandboxTask("testbed", "Testbed", "A simple sandbox for messing around.") {
override def call(app: Application, services: ServiceRegistry, argument: Option[String])(implicit trace: TraceData) = {
override def call(cfg: Config)(implicit trace: TraceData) = {
Future.successful("OK")
}
}

case object TracingTest extends SandboxTask("tracing", "Tracing Test", "A tracing test.") {
override def call(app: Application, services: ServiceRegistry, argument: Option[String])(implicit trace: TraceData) = TracingLogic.go(app, argument)
override def call(cfg: Config)(implicit trace: TraceData) = TracingLogic.go(cfg.app, cfg.argument)
}

case object CopyTable extends SandboxTask("copyTable", "Copy Table", "Copy a database table, in preparation for new DDL.") {
override def call(app: Application, services: ServiceRegistry, argument: Option[String])(implicit trace: TraceData) = {
argument.map(a => TableLogic.copyTable(app, a)).getOrElse(Future.successful("Argument required."))
override def call(cfg: Config)(implicit trace: TraceData) = {
cfg.argument.map(a => TableLogic.copyTable(cfg.app, a)).getOrElse(Future.successful("Argument required."))
}
}

case object RestoreTable extends SandboxTask("restoreTable", "Restore Table", "Restores a database table.") {
override def call(app: Application, services: ServiceRegistry, argument: Option[String])(implicit trace: TraceData) = {
argument.map(a => TableLogic.restoreTable(app, a)).getOrElse(Future.successful("Argument required."))
override def call(cfg: Config)(implicit trace: TraceData) = {
cfg.argument.map(a => TableLogic.restoreTable(cfg.app, a)).getOrElse(Future.successful("Argument required."))
}
}

case object DatabaseBackup extends SandboxTask("databaseBackup", "Database Backup", "Backs up the database.") {
override def call(app: Application, services: ServiceRegistry, argument: Option[String])(implicit trace: TraceData) = {
override def call(cfg: Config)(implicit trace: TraceData) = {
Future.successful(BackupRestore.backup())
}
}

case object DatabaseRestore extends SandboxTask("databaseRestore", "Database Restore", "Restores the database.") {
override def call(app: Application, services: ServiceRegistry, argument: Option[String])(implicit trace: TraceData) = {
override def call(cfg: Config)(implicit trace: TraceData) = {
Future.successful(BackupRestore.restore("TODO"))
}
}

case object Graphdoc extends SandboxTask("graphdoc", "Graphdoc", "Runs graphdoc against this project's schema.") {
override def call(cfg: Config)(implicit trace: TraceData) = {
GraphdocLogic.generate(Credentials.system, cfg.app, cfg.graphQLService, cfg.argument)
}
}

override val values = findValues
}
2 changes: 1 addition & 1 deletion app/services/graphql/GraphQLService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class GraphQLService @javax.inject.Inject() (tracing: TracingService, registry:
val ret = Executor.execute(
schema = Schema.schema,
queryAst = ast,
userContext = GraphQLContext(app, registry, creds, td),
userContext = GraphQLContext(app, this, registry, creds, td),
operationName = operation,
variables = variables.getOrElse(Json.obj()),
deferredResolver = Schema.resolver,
Expand Down
99 changes: 99 additions & 0 deletions conf/IntrospectionQuery.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
query IntrospectionQuery {
__schema {
queryType { name description kind}
mutationType { name description kind }
subscriptionType { name description kind }
types {
name
kind
description
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}

fragment FullType on __Type {
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}

fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
}

fragment TypeRef on __Type {
kind
name
description
ofType {
kind
name
description
ofType {
kind
name
description
ofType {
kind
name
description
ofType {
kind
name
description
ofType {
kind
name
description
ofType {
kind
name
description
ofType {
kind
name
description
}
}
}
}
}
}
}
}
9 changes: 0 additions & 9 deletions data/test/curl.txt

This file was deleted.

23 changes: 0 additions & 23 deletions data/test/raw.txt

This file was deleted.

0 comments on commit 54db553

Please sign in to comment.