Refactoring live-docs

This commit is contained in:
Simon Redfern 2015-08-20 22:05:49 +02:00
parent 4d00adc7a9
commit c9b80a708b
3 changed files with 126 additions and 102 deletions

View File

@ -5,8 +5,7 @@ import net.liftweb.json.JObject
import net.liftweb.json.JsonDSL._
import net.liftweb.common.Box
import net.liftweb.common.Full
import net.liftweb.json.JsonAST.JValue
import net.liftweb.json.JsonAST.JInt
import net.liftweb.json.JsonAST.{JValue, JInt}
import net.liftweb.json.JDouble
import net.liftweb.common.Empty
import java.net.URL
@ -627,19 +626,36 @@ object ObpJson {
case class PermissionsJson(permissions : Option[List[PermissionJson]])
////////////////////////////////////////
// Copied from OBP-API JSONFactory1_4_0
// TODO: Import these and others from API jar file?
// Import these from API jar file?
// Matches OBP-API representation of Resource Docs etc. Used to describe where an API call is implemented
case class ImplementedByJson (
version : String, // Short hand for the version e.g. "1_4_0" means Implementations1_4_0
function : String // The val / partial function that implements the call e.g. "getBranches"
)
// Used to describe the OBP API calls for documentation and API discovery purposes
case class ResourceDocJson(id: Int,
verb: String,
url: String,
description: String)
request_verb: String,
request_url: String,
description: String,
request_body: JValue,
response_body: JValue,
implemented_by: ImplementedByJson)
case class ResourceDocsJson (resource_docs : List[ResourceDocJson])
///////////////////////////////////////////
// Internal representation of the ResourceDoc (may differ from the OBP API representation)
case class ResourceDoc(id: Int,
verb: String,
url: String,
description: String)
description: String,
request_body: JValue)
case class ResourceDocs (resourceDocs : List[ResourceDoc])

View File

@ -3,14 +3,19 @@ package code.snippet
import _root_.net.liftweb._
import code.lib.ObpJson.ResourceDoc
import code.lib.{ObpPost, ObpGet}
//import code.snippet.CallUrlForm._
import net.liftweb.http.S
import net.liftweb.json.{JsonParser, JsonAST}
import net.liftweb.json.{Extraction, JsonParser, JsonAST}
import net.liftweb.json.JsonAST.{JField, JObject, JValue}
import _root_.scala.xml.{NodeSeq, Text}
import net.liftweb._
// for compact render
import net.liftweb.json._
import common._
import net.liftweb.util.Helpers._
@ -28,32 +33,26 @@ Present a list of OBP resource URLs
*/
class LiveDocs extends Loggable {
def showResources = {
case class Resource(id: String,
verb: String,
url: String,
description: String,
representation: String)
// Get the requested version from the url parameter and default if none
val apiVersion = S.param("api-version").getOrElse("1.4.0")
val apiVersionRequested = S.param("api-version").getOrElse("1.4.0")
if (apiVersion != "1.4.0") S.notice("Note: Only 1.4.0 is currently supported")
if (apiVersionRequested != "1.4.0") S.notice("Note: Only 1.4.0 is currently supported")
logger.info (s"API version requested is: $apiVersion")
logger.info (s"API version requested is: $apiVersionRequested")
// Get a list of resource docs from the API server
// This will throw exception if resource_docs key is not populated
// Convert the json representation to ResourceDoc (pretty much a one to one mapping)
val resources = for {
r <- getResourceDocsJson.map(_.resource_docs).get
} yield ResourceDoc(id = r.id, verb = r.verb, url = r.url, description = r.description)
} yield ResourceDoc(id = r.id, verb = r.request_verb, url = r.request_url, description = r.description, request_body = r.request_body)
// Render the resources into a (nested) table.
// This could probably be improved.
// So far we can't (why?) set value of input field at render time like this ".resource_id_input [value]" #> s"lslslsls${i.id}"
// so have to work around and set the input fields via the try_me_button onclick javascript.
// Notes on escaping strings
// Notes on escaping strings:
// To have a $ in the resulting string use two: $$
// Can't escape " with \" or use triple quoted string in the string interpolation so use the replace hack
@ -66,9 +65,72 @@ class LiveDocs extends Loggable {
}
// Constant
val apiVersion = "v1.4.0"
var resourceId = ""
var requestVerb = ""
var requestUrl = ""
var requestBody = "{}"
def process(): JsCmd = {
logger.info(s"requestUrl is $requestUrl")
logger.info(s"resourceId is $resourceId")
// Create json object from input string
val jsonObject = JsonParser.parse(requestBody).asInstanceOf[JObject]
// Call the url with optional body and put the response into the appropriate result div
SetHtml("result_" + resourceId, Text(getResponse(apiVersion, requestUrl, requestVerb, jsonObject))) &
// This applies json highlighting to the json
Run ("$('pre code').each(function(i, block) { hljs.highlightBlock(block);});")
}
def getResponse (apiVersion : String, url : String, resourceVerb: String, json : JValue) : String = {
implicit val formats = net.liftweb.json.DefaultFormats
val urlWithVersion = s"/$apiVersion$url"
// TODO: Handle POST requests
val responseBodyBox = {
resourceVerb match {
case "GET" => ObpGet(urlWithVersion)
case "POST" => ObpPost(urlWithVersion, json)
case _ => {
val failMsg = s"Live Docs says: Unsupported resourceVerb: $resourceVerb. Url requested was: $url"
logger.warn(failMsg)
Failure(failMsg)
}
}
}
logger.info(s"responseBodyBox is ${responseBodyBox}")
// Handle the contents of the Box
val responseBody =
responseBodyBox match {
case Full(json) => writePretty(json)
case Empty => "Empty: API did not return anything"
case Failure(message, _, _) => "Failure: " + message
}
logger.info(s"responseBody is $responseBody")
responseBody
}
// In case we use Extraction.decompose
implicit val formats = net.liftweb.json.DefaultFormats
// Note! This is the return of the function.
// All the replacements you want to do *must be chained here together at the end of the function*.
// Also, you can't chain replaceWith (alias for #>)
// Also, you can't use replaceWith (the alias for #>) to chain
// See the following for some examples.
// http://blog.knoldus.com/2013/03/08/lift-web-basics-of-using-snippets-for-a-beginner/
@ -84,86 +146,29 @@ class LiveDocs extends Loggable {
".resource_url" #> i.url &
".resource_description" #> i.description &
".resource_representation" #> "JSON" &
".resource_url_td [id]" #> s"resource_url_td_${i.id}" &
".resource_verb_td [id]" #> s"resource_verb_td_${i.id}" &
".resource_url_td [id]" #> s"resource_url_td_${i.id}" & // Probably don't need this now
".resource_verb_td [id]" #> s"resource_verb_td_${i.id}" & // Probably don't need this now
".url_caller [id]" #> s"url_caller_${i.id}" &
"@request_url_input [id]" #> s"request_url_input_${i.id}" &
"@request_url_input [value]" #> s"${i.url}" &
".try_me_button [onclick]" #> s"$$(DOUBLE-QUOTE#url_caller_${i.id}DOUBLE-QUOTE).fadeToggle(); $$(DOUBLE-QUOTE#request_url_input_${i.id}DOUBLE-QUOTE).val($$(DOUBLE-QUOTE#resource_url_td_${i.id}DOUBLE-QUOTE)[0].innerHTML); $$(DOUBLE-QUOTE#resource_id_input_${i.id}DOUBLE-QUOTE).val(DOUBLE-QUOTE${i.id}DOUBLE-QUOTE); $$(DOUBLE-QUOTE#request_verb_input_${i.id}DOUBLE-QUOTE).val($$(DOUBLE-QUOTE#resource_verb_td_${i.id}DOUBLE-QUOTE)[0].innerHTML);".replaceAll("DOUBLE-QUOTE",""""""") &
".try_me_button [onclick]" #> s"$$(DOUBLE-QUOTE#url_caller_${i.id}DOUBLE-QUOTE).fadeToggle();".replaceAll("DOUBLE-QUOTE",""""""") &
".result [id]" #> s"result_${i.id}" &
"@resource_id_input [id]" #> s"resource_id_input_${i.id}" &
"@request_verb_input [id]" #> s"request_verb_input_${i.id}" &
"@request_body [id]" #> s"request_body_${i.id}" &
"@request_body [style]" #> s"display: ${displayBody(i.verb)};"
}
}
}
/*
Call an OBP URL and return the response to the browser in JSON form.
*/
object CallUrlForm extends Loggable {
def getResponse (apiVersion : String, url : String, resourceVerb: String, json : JValue) : String = {
implicit val formats = net.liftweb.json.DefaultFormats
// TODO: Handle POST requests
val responseBodyBox = {
resourceVerb match {
case "GET" => ObpGet(url)
case "POST" => ObpPost(url, json)
case _ => {
val failMsg = s"Live Docs says: Unsupported resourceVerb: $resourceVerb."
logger.warn(failMsg)
Failure(failMsg)
}
}
}
logger.info(s"responseBodyBox is ${responseBodyBox}")
// Handle the contents of the Box
val responseBody =
responseBodyBox match {
case Full(json) => writePretty(json)
case Empty => "Empty: API did not return anything"
case Failure(message, _, _) => "Failure: " + message
}
logger.info(s"responseBody is $responseBody")
responseBody
}
def render = {
var apiVersion = "/obp/v1.4.0/"
var resourceId = ""
var requestVerb = ""
var requestUrl = ""
var requestBody = "{}"
def process(): JsCmd = {
// Create json object from input string
val jsonObject = JsonParser.parse(requestBody).asInstanceOf[JObject]
// Call the url with optional body and put the response into the appropriate result div
SetHtml("result_" + resourceId, Text(getResponse(apiVersion, requestUrl, requestVerb, jsonObject))) &
// This applies json highlighting to the json
Run ("$('pre code').each(function(i, block) { hljs.highlightBlock(block);});")
}
"@request_body [style]" #> s"display: ${displayBody(i.verb)};" &
//////
// The form field (on the left) is bound to the variable (urlToCall)
// (However, updating the var here does not seem to update the form field value)
"@resource_id_input" #> text(resourceId, s => resourceId = s, "type" -> "hidden") &
"@request_verb_input" #> text(requestVerb, s => requestVerb = s, "type" -> "hidden") &
"@request_url_input" #> text(requestUrl, s => requestUrl = s, "maxlength" -> "255", "size" -> "100") &
"@request_body_input" #> text(requestBody, s => requestBody = s, "type" -> "text") &
// TODO use this approach.
// We provide a default value (i.url) and bind the user input to requestUrl. requestURL is available in the function process
"@request_url_input" #> text(i.url, s => requestUrl = s, "maxlength" -> "255", "size" -> "100", "id" -> s"request_url_input_${i.id}") &
// Extraction.decompose creates json representation of JObject.
"@request_body_input" #> text(pretty(render(i.request_body)), s => requestBody = s, "type" -> "text") &
// We're not using the id at the moment
"@request_verb_input" #> text(i.verb, s => requestVerb = s, "type" -> "text", "id" -> s"request_verb_input_${i.id}") &
"@resource_id_input" #> text(i.id.toString, s => resourceId = s, "type" -> "text", "id" -> s"resource_id_input_${i.id}") &
// Replace the type=submit with Javascript that makes the ajax call.
"type=submit" #> ajaxSubmit("Call", process)
// ".call_button" #> ajaxSubmit("Call", process(i.id, apiVersion, i.url, i.verb,"{}"))
//".call_button" #> ajaxSubmit("Call", process(123, "apiVersion", "i.url", "i.verb","{}"))
".call_button" #> ajaxSubmit("Call", process)
// def process(resourceId: String, apiVersion : String, requestUrl: String, requestVerb: String, requestBody: String = "{}"): JsCmd = {
}
}
}

View File

@ -30,21 +30,24 @@
</tr>
<!-- This section starts hidden and is shown with the Try Me button -->
<tr class="url_caller" style="display: none;">
<tr class="url_caller" style="display: block;">
<td colspan="5">
<form class="lift:form.ajax">
<div class="lift:CallUrlForm">
<div class="xxxlift:LiveDocs">
<!-- Attributes for these fields are replaced -->
<input name="resource_id_input">
<input name="request_verb_input">
<!-- The url_to_call is populated with the Try Me button (but it can be edited manually) -->
<input name="request_verb_input" placeholder="GET or POST etc.">
<!-- The url_to_call is populated at render (but it can be edited manually) -->
<input name="request_url_input" placeholder="OBP URL to call">
<div name="request_body" style="display: block;">
<input name="request_body_input" placeholder="JSON body to POST">
</div>
<input name="test_input" placeholder="Test input">
<!-- This submit button is also modified by LiveDocs.scala -->
<input type="submit">
<input class="call_button" type="submit">
</div>
</form>
<!-- API call result goes here It is highlighted by javascript called from the submit button -->