Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Live search terms from our search pages?

Maybe. Not tested against a live feed but may work :)
  • Loading branch information...
commit a750f98bde7f1d64817e4470abf7d381ad563006 1 parent 03c8ebb
@tackley authored
View
29 app/assets/js/search-terms.coffee
@@ -0,0 +1,29 @@
+jQuery ->
+
+ deleteMoreThanTen = ->
+ items = $('.search-terms p')
+ items[10..].fadeOut()
+ items[11..].remove()
+
+ formatParams = (params) ->
+ p = for key, value of params
+ "#{key}=#{value}"
+ p.join " "
+
+ highestDt = 0
+
+ setInterval ( ->
+ $.ajax "/api/search",
+ type: 'GET'
+ dataType: 'jsonp'
+ data: { since: highestDt }
+ success: (data) ->
+ for searchTerm in data
+ highestDt = searchTerm.dt
+ html = $("<p>#{searchTerm.q}<span class=other-terms>#{formatParams(searchTerm.otherParams)}</span></p>")
+ $('.search-terms').prepend(html.hide())
+ html.slideDown()
+
+ deleteMoreThanTen()
+ ), 1500
+
View
4 app/assets/stylesheets/_search.less
@@ -0,0 +1,4 @@
+.search-terms {
+ p { font-size: 30pt; line-height: 0.9 }
+ .other-terms { font-size: 10pt; display: block; margin-left: 20px; }
+}
View
23 app/assets/stylesheets/std.less
@@ -0,0 +1,23 @@
+body {
+ padding-top: 40px;
+}
+
+html, body {
+ background-color: #eee;
+}
+
+/* The white background content wrapper */
+.content {
+ background-color: #fff;
+ padding: 20px;
+ margin: 0 -20px; /* negative indent the amount of the padding to maintain the grid system */
+ -webkit-border-radius: 0 0 6px 6px;
+ -moz-border-radius: 0 0 6px 6px;
+ border-radius: 0 0 6px 6px;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+ -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+}
+
+@import "_search";
+
View
7 app/controllers/Api.scala
@@ -22,6 +22,13 @@ object Api extends Controller {
}
}
+ def search(callback: Option[String], since: Long) = Action {
+ withCallback(callback) {
+ val response = Backend.liveSearchTerms.get.filter(_.dt > since).sortBy(_.dt)
+ Serialization.write(response)
+ }
+ }
+
private def tidy(s: String) = s match {
case "0.0" => "trace"
case other => other
View
2  app/controllers/Application.scala
@@ -15,5 +15,7 @@ object Application extends Controller {
def top20chart = Action { Ok(views.html.snippets.top20chart(Backend.currentLists.get)) }
def details = Action { Ok(views.html.details(Backend.currentLists.get.everything)) }
+
+ def search = Action { Ok(views.html.search()) }
}
View
12 app/lib/Backend.scala
@@ -1,16 +1,19 @@
package lib
import akka.actor.Actor._
-import akka.actor.Scheduler
import java.util.concurrent.TimeUnit
import org.joda.time.DateTime
+import akka.actor.{Supervisor, Scheduler}
+import akka.config.Supervision._
+
object Backend {
- val listener = actorOf[EventListener].start()
+ val listener = actorOf[ClickStreamActor].start()
val calculator = actorOf[Calculator].start()
+ val searchTerms = actorOf[SearchTermActor].start()
- val mqReader = new MqReader(listener)
+ val mqReader = new MqReader(listener :: searchTerms :: Nil)
def start() {
Scheduler.restart()
@@ -21,6 +24,7 @@ object Backend {
}
listener ! Event("1.1.1.1", new DateTime(), "/dummy", "GET", 200, Some("http://www.google.com"), "my agent", "geo!")
+ searchTerms ! Event("1.1.1.1", new DateTime(), "/search?q=dummy&a=b&c=d%2Fj", "GET", 200, Some("http://www.google.com"), "my agent", "geo!")
}
def stop() {
@@ -34,4 +38,6 @@ object Backend {
def currentLists = currentStats.map(_._2)
def currentHits = currentStats.map(_._1).get
+
+ def liveSearchTerms = (searchTerms ? GetSearchTerms()).as[List[GuSearchTerm]]
}
View
2  app/lib/EventListener.scala → app/lib/ClickStreamActor.scala
@@ -30,7 +30,7 @@ case class ClickStream(allClicks: GenSeq[Event], lastUpdated: DateTime, firstUpd
}
-class EventListener extends Actor {
+class ClickStreamActor extends Actor {
var clickStream = ClickStream(Nil.par, DateTime.now, DateTime.now)
protected def receive = {
View
11 app/lib/MqReader.scala
@@ -9,14 +9,14 @@ object MqReader {
val SCALE_TO_FULL_SITE = 10
}
-class MqReader(actor: ActorRef) {
+class MqReader(consumers: List[ActorRef]) {
val logger = Logger(getClass)
var keepRunning = true
def stop() {
logger.info("waiting for stop...")
keepRunning = false
- Thread.sleep(2000)
+ Thread.sleep(500)
logger.info("stop has hopefully happened")
}
@@ -24,8 +24,8 @@ class MqReader(actor: ActorRef) {
val context = ZMQ.context(1)
val sub = context.socket(ZMQ.SUB)
- sub.connect("tcp://gnmfasteragain:5100")
- sub.connect("tcp://gnmfasteragain:5200")
+ sub.connect("tcp://localhost:5100")
+ sub.connect("tcp://localhost:5200")
sub.subscribe(Array.empty)
sub.setHWM(50)
@@ -46,7 +46,8 @@ class MqReader(actor: ActorRef) {
e.path.startsWith("/global/adcode/generate")
}
- event.foreach { actor ! }
+ for (e <- event; a <- consumers) a ! e
+
} while (keepRunning)
sub.close()
View
34 app/lib/SearchTermActor.scala
@@ -0,0 +1,34 @@
+package lib
+
+import akka.actor.Actor
+import org.jboss.netty.handler.codec.http.QueryStringDecoder
+import scala.collection.JavaConversions._
+import org.joda.time.DateTime
+
+
+case class GetSearchTerms()
+
+case class GuSearchTerm(
+ dt: Long,
+ q: String,
+ otherParams: Map[String, String]
+)
+
+class SearchTermActor extends Actor {
+ private var terms: List[GuSearchTerm] = Nil
+
+ protected def receive = {
+ case e: Event =>
+ if (e.path == "/search") {
+ val params = new QueryStringDecoder(e.url).getParameters.map{ case (k, v) => k -> v.head }.toMap
+
+ for (q <- params.get("q")) {
+ terms = (terms :+ GuSearchTerm(System.currentTimeMillis(), q, params - "q")).take(20)
+ }
+ }
+
+ case GetSearchTerms() =>
+ self.channel ! terms
+ }
+}
+
View
43 app/views/search.scala.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Search</title>
+ <link href='http://fonts.googleapis.com/css?family=News+Cycle' rel='stylesheet' type='text/css'>
+ @views.html.snippets.includes()
+
+ <link rel="stylesheet" href='@routes.Assets.at("stylesheets/std.css")'>
+
+ <script defer src='@routes.Assets.at("js/search-terms.js")'></script>
+</head>
+<body>
+
+@views.html.snippets.topnav()
+
+<div class="container">
+
+ <div class="content">
+ <div class="page-header">
+ <h1>Live search terms
+ <small>on www.guardian.co.uk/search</small>
+ </h1>
+ </div>
+
+ <div class="row">
+ <div class="span-two-thirds search-terms">
+
+ <!--
+ <p>blah <span class="other-terms">tag=blah orderby=do</span></p>
+ <p>two</p>
+
+ -->
+
+ </div>
+ </div>
+
+ </div>
+
+
+</div>
+
+</body>
+</html>
View
1  app/views/snippets/topnav.scala.html
@@ -5,6 +5,7 @@
<ul class="nav">
<li><a href="@routes.Application.top10()">Big Top 10</a></li>
<li><a href="@routes.Application.top20()">Top 20</a></li>
+ <li><a href="@routes.Application.search()">Search</a></li>
</ul>
</div>
</div>
View
1  app/views/top20.scala.html
@@ -3,7 +3,6 @@
<html>
<head>
<title>Top 20</title>
- <link href='http://fonts.googleapis.com/css?family=News+Cycle' rel='stylesheet' type='text/css'>
@views.html.snippets.includes()
<style>
View
4 conf/routes
@@ -12,7 +12,11 @@ GET /top20chart controllers.Application.top20chart()
GET /details controllers.Application.details()
+GET /search controllers.Application.search()
+
GET /api/counts controllers.Api.counts(callback: Option[String])
+GET /api/search controllers.Api.search(callback: Option[String], since: Long ?= 0)
+
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
View
35 local_pub.rb
@@ -0,0 +1,35 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'zmq'
+
+unless ARGV.length == 1
+ puts "usage: local_pub.rb file"
+ exit
+end
+
+file = ARGV[0]
+port = 5100
+
+puts "Publishing on port #{port}..."
+
+context = ZMQ::Context.new(1)
+publisher = context.socket(ZMQ::PUB)
+publisher.setsockopt(ZMQ::HWM, 10);
+publisher.bind("tcp://*:#{port}")
+
+puts "Waiting a bit for 0mq to settle down..."
+sleep 5
+
+
+File.new(file, "r").each_line do |l|
+ puts "sending: #{l}"
+ publisher.send(l)
+end
+
+publisher.close
+
+puts "All sent, waiting a bit more for 0mq to finish sending..."
+sleep 5
+puts "Bye!"
+
View
2  project/Build.scala
@@ -5,7 +5,7 @@ import PlayProject._
object ApplicationBuild extends Build {
val appName = "live-dashboard"
- val appVersion = "1.0"
+ val appVersion = "1.1"
val appDependencies = Seq(
"org.zeromq" %% "zeromq-scala-binding" % "0.0.1-SNAPSHOT",
View
4 reader.rb
@@ -7,9 +7,9 @@
sub = context.socket(ZMQ::SUB)
sub.setsockopt(ZMQ::HWM, 10);
sub.setsockopt(ZMQ::SUBSCRIBE, "")
-sub.connect("tcp://gnmfasteragain.int.gnl:5536")
+sub.connect("tcp://localhost:5100")
-for i in 1..10 do
+for i in 1..1000 do
msg = sub.recv(0)
puts "received: " + msg
end
Please sign in to comment.
Something went wrong with that request. Please try again.