diff --git a/Gemfile b/Gemfile index 81a431c..9aed1b3 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,11 @@ gem 'jquery-rails' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 1.2' +# Versioning +gem 'paper_trail', '~> 4.0.0.rc' + +gem "highcharts-rails", "~> 4.1.5" + group :doc do # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 2127645..24d369e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,6 +49,8 @@ GEM erubis (2.7.0) execjs (1.4.0) multi_json (~> 1.0) + highcharts-rails (4.1.5) + railties (>= 3.1) hike (1.2.3) i18n (0.6.4) jbuilder (1.5.0) @@ -70,6 +72,10 @@ GEM mime-types (1.23) minitest (4.7.5) multi_json (1.7.8) + paper_trail (4.0.0.rc1) + activerecord (>= 3.0, < 6.0) + activesupport (>= 3.0, < 6.0) + request_store (~> 1.1.0) pg (0.15.1) polyglot (0.3.3) postmark (1.0.1) @@ -103,6 +109,7 @@ GEM rdoc (3.12.2) json (~> 1.4) ref (1.0.5) + request_store (1.1.0) sass (3.2.10) sass-rails (4.0.0) railties (>= 4.0.0.beta, < 5.0) @@ -141,9 +148,11 @@ PLATFORMS DEPENDENCIES coffee-rails (~> 4.0.0) + highcharts-rails (~> 4.1.5) jbuilder (~> 1.2) jquery-rails less-rails + paper_trail (~> 4.0.0.rc) pg postmark-rails rails (= 4.0.0) @@ -154,3 +163,6 @@ DEPENDENCIES therubyracer twitter-bootstrap-rails! uglifier (>= 1.3.0) + +BUNDLED WITH + 1.10.1 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 06170c8..15e3c0c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,4 +13,7 @@ //= require jquery //= require jquery_ujs //= require twitter/bootstrap +//= require highcharts +//= require highcharts/highcharts-more +//= require highcharts/themes/dark-unica //= require_tree . diff --git a/app/assets/javascripts/papers.js.coffee b/app/assets/javascripts/papers.js.coffee index 0038185..d5f77be 100644 --- a/app/assets/javascripts/papers.js.coffee +++ b/app/assets/javascripts/papers.js.coffee @@ -19,39 +19,47 @@ totalWordsIdentifier = "num_words"; distinctLevels = [50, 100, 200, 300, 500, 800, 1000, 1250, 1500]; distinctIdentifier = "different_words"; +paper_etag = undefined + @updateSketches = (id) -> - $.ajax id.toString(), - type: 'GET' - dataType: 'json' - error: (jqXHR, textStatus, errorThrown) -> - console.log "WARNING: Couldn't get current stats" - success: (data, textStatus, jqXHR) -> - console.log "SUCCESS: Got current stats, updating sketches..." + $.ajax "#{id.toString()}.json", + type: 'GET' + dataType: 'json' + error: (jqXHR, textStatus, errorThrown) -> + console.log "WARNING: Couldn't get current stats" + success: (data, textStatus, jqXHR) -> + if paper_etag != jqXHR.getResponseHeader("Etag") + console.log "SUCCESS: Got current stats, updating sketches..." + for sketch of sketches + updateSketch(sketch, data) - for sketch of sketches - updateSketch(sketch, data) + if @chart + updateTimeline(data["history"]) + else + initTimeline(data["history"]) + paper_etag = jqXHR.getResponseHeader("Etag") @updateSketch = (sketch, data) -> - # Sketches aren't loaded when document is ready - # --> wait for them to load, since Processing.js provides no callback - timer = 0 - timeout = 2000 - clearInterval(mem) - mem = setInterval -> - instance = Processing.getInstanceById(sketch); - if instance - if not sketches[sketch] - # Load this sketch for the first time - initSketch(sketch, instance) - instance.update($.parseJSON(data["stats"])); - clearInterval(mem); - else - timer += 10 - if timer > timeout - console.log("WARNING: Failed to load sketch"); - clearInterval(mem); - , 10 + # Sketches aren't loaded when document is ready + # --> wait for them to load, since Processing.js provides no callback + timer = 0 + timeout = 2000 + clearInterval(mem) + mem = setInterval -> + instance = Processing.getInstanceById(sketch); + if instance + if not sketches[sketch] + # Load this sketch for the first time + initSketch(sketch, instance) + instance.update(data["stats"]); + clearInterval(mem); + else + timer += 10 + if timer > timeout + console.log("WARNING: Failed to load sketch"); + clearInterval(mem); + , 10 @initSketch = (sketch, instance) -> @@ -64,14 +72,89 @@ distinctIdentifier = "different_words"; instance.setLevels(distinctLevels); else # Do nothing - sketches[sketch] = 1 + sketches[sketch] = 1 + +updateTimeline = (data) -> + words = [] + pages = [] + + $.each data, (key,value) -> + words.push([Date.parse(value.time), value.words]) + pages.push([Date.parse(value.time), value.pages]) + + @chart.series[0].update + data: words + @chart.series[1].update + data: pages +initTimeline = (data) -> + words = [] + pages = [] + + $.each data, (key,value) -> + words.push([Date.parse(value.time), value.words]) + pages.push([Date.parse(value.time), value.pages]) + + @chart = $('#timeline').highcharts + chart: + type: 'spline' + zoomType: 'x' + backgroundColor:'black' + title: text: '' + xAxis: + type: 'datetime' + dateTimeLabelFormats: + month: '%e. %b' + year: '%Y' + title: text: 'Date' + yAxis: [ + { + title: text: 'Words' + min: 0 + } + { + title: text: 'Pages' + min: 0 + } + ] + tooltip: + headerFormat: '{series.name}
' + pointFormat: '{point.x:%e. %b}: {point.y:.2f}' + plotOptions: spline: marker: enabled: true + series: [ + { + name: 'Words' + data: words + yAxis: 0 + zoneAxis: 'x' + zones: [ + { value: words[words.length - 2][0] } + { dashStyle: 'dot' } + ] + connectNulls: true + } + { + name: 'Pages' + data: pages + yAxis: 1 + zoneAxis: 'x' + zones: [ + { value: pages[pages.length - 2][0] } + { dashStyle: 'dot' } + ] + connectNulls: true + } + ] $ -> - if $('.visualization').length > 0 # See if we're on a paper_show page - paper_id = $('#paper_id').html() - updateSketches(paper_id) + if $('.visualization').length > 0 # See if we're on a paper_show page + Highcharts.setOptions + global: + useUTC: false + + paper_id = $('#paper_id').html() + updateSketches(paper_id) - setInterval -> - updateSketches(paper_id) - , update_interval + setInterval -> + updateSketches(paper_id) + , update_interval diff --git a/app/controllers/papers_controller.rb b/app/controllers/papers_controller.rb index cd5ef91..bbcc4b2 100644 --- a/app/controllers/papers_controller.rb +++ b/app/controllers/papers_controller.rb @@ -11,6 +11,7 @@ def index # GET /papers/1 # GET /papers/1.json def show + fresh_when last_modified: @paper.updated_at.utc, etag: @paper end # GET /papers/new diff --git a/app/models/paper.rb b/app/models/paper.rb index e05e849..81101c5 100644 --- a/app/models/paper.rb +++ b/app/models/paper.rb @@ -1,3 +1,46 @@ class Paper < ActiveRecord::Base has_many :applause + + has_paper_trail skip: [:created_at, :updated_at] + + def history + history = [start] + history += versions.map do |version| + { + time: version.created_at, + words: JSON.parse(version.reify.stats)['num_words'], + pages: JSON.parse(version.reify.stats)['pages'] + } + end + history.append current + history.append goal + end + + def start + { + time: start_date, + words: 0, + pages: 0 + } + end + + def current + { + time: updated_at, + words: JSON.parse(stats)['num_words'], + pages: JSON.parse(stats)['pages'] + } + end + + def goal + goal = { + time: end_date, + words: nil, + pages: nil + } + if end_date && goal_value && %w(pages words).include?(goal_type) + goal[goal_type.to_sym] = goal_value + end + goal + end end diff --git a/app/views/papers/show.html.erb b/app/views/papers/show.html.erb index 0eea8ba..fac0319 100644 --- a/app/views/papers/show.html.erb +++ b/app/views/papers/show.html.erb @@ -6,7 +6,7 @@ <%= render "applause" %>

<%= link_to 'BACK TO ALL PAPERS', papers_path %>

-<% if @paper.alive %> +<% if @paper.alive %>

I'm currently typing ... !

<% end %>
@@ -31,6 +31,10 @@ >
+
+

History

+
+

Oxford 3000+ coverage

diff --git a/app/views/papers/show.json.jbuilder b/app/views/papers/show.json.jbuilder index 6bba8dd..04ffe28 100644 --- a/app/views/papers/show.json.jbuilder +++ b/app/views/papers/show.json.jbuilder @@ -1 +1,2 @@ -json.extract! @paper, :title, :stats, :created_at, :updated_at +json.extract! @paper, :title, :created_at, :updated_at, :history +json.stats JSON.parse(@paper.stats) diff --git a/db/migrate/20150607235217_create_versions.rb b/db/migrate/20150607235217_create_versions.rb new file mode 100644 index 0000000..23be970 --- /dev/null +++ b/db/migrate/20150607235217_create_versions.rb @@ -0,0 +1,13 @@ +class CreateVersions < ActiveRecord::Migration + def change + create_table :versions do |t| + t.string :item_type, :null => false + t.integer :item_id, :null => false + t.string :event, :null => false + t.string :whodunnit + t.text :object + t.datetime :created_at + end + add_index :versions, [:item_type, :item_id] + end +end diff --git a/db/migrate/20150608195034_add_dates_and_goal_to_paper.rb b/db/migrate/20150608195034_add_dates_and_goal_to_paper.rb new file mode 100644 index 0000000..8a9745c --- /dev/null +++ b/db/migrate/20150608195034_add_dates_and_goal_to_paper.rb @@ -0,0 +1,8 @@ +class AddDatesAndGoalToPaper < ActiveRecord::Migration + def change + add_column :papers, :start_date, :datetime + add_column :papers, :end_date, :datetime + add_column :papers, :goal_value, :integer + add_column :papers, :goal_type, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 21417d6..4d04f68 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,10 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20131031130746) do - - # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" +ActiveRecord::Schema.define(version: 20150608195034) do create_table "applauses", force: true do |t| t.integer "paper_id" @@ -31,6 +28,21 @@ t.datetime "created_at" t.datetime "updated_at" t.boolean "alive" + t.datetime "start_date" + t.datetime "end_date" + t.integer "goal_value" + t.string "goal_type" end + create_table "versions", force: true do |t| + t.string "item_type", null: false + t.integer "item_id", null: false + t.string "event", null: false + t.string "whodunnit" + t.text "object" + t.datetime "created_at" + end + + add_index "versions", ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id", using: :btree + end