diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/admin_general_controller.rb | 59 | ||||
-rw-r--r-- | app/controllers/admin_request_controller.rb | 17 | ||||
-rw-r--r-- | app/controllers/api_controller.rb | 46 | ||||
-rw-r--r-- | app/controllers/public_body_controller.rb | 15 | ||||
-rw-r--r-- | app/controllers/request_controller.rb | 12 | ||||
-rw-r--r-- | app/controllers/request_game_controller.rb | 13 | ||||
-rw-r--r-- | app/models/info_request.rb | 52 | ||||
-rw-r--r-- | app/models/info_request_event.rb | 22 | ||||
-rw-r--r-- | app/models/public_body.rb | 13 | ||||
-rw-r--r-- | app/models/request_classification.rb | 16 | ||||
-rw-r--r-- | app/views/admin_general/timeline.rhtml | 24 | ||||
-rw-r--r-- | app/views/admin_request/list_old_unclassified.rhtml | 3 | ||||
-rw-r--r-- | app/views/api/request_events.atom.builder | 2 | ||||
-rw-r--r-- | app/views/request_game/play.rhtml | 12 |
14 files changed, 205 insertions, 101 deletions
diff --git a/app/controllers/admin_general_controller.rb b/app/controllers/admin_general_controller.rb index 2c961dfc5..7e8498d8a 100644 --- a/app/controllers/admin_general_controller.rb +++ b/app/controllers/admin_general_controller.rb @@ -61,16 +61,59 @@ class AdminGeneralController < AdminController @events_title = "Events, all time" date_back_to = Time.now - 1000.years end - @events = InfoRequestEvent.find(:all, :order => "created_at desc, id desc", - :conditions => ["created_at > ? ", date_back_to.getutc]) - @public_body_history = PublicBody.versioned_class.find(:all, :order => "updated_at desc, id desc", - :conditions => ["updated_at > ? ", date_back_to.getutc]) - for pbh in @public_body_history - pbh.created_at = pbh.updated_at + + # Get an array of event attributes within the timespan in the format + # [id, type_of_model, event_timestamp] + # Note that the relevent date for InfoRequestEvents is creation, but + # for PublicBodyVersions is update thoughout + connection = InfoRequestEvent.connection + timestamps = connection.select_rows("SELECT id,'InfoRequestEvent', + created_at AS timestamp + FROM info_request_events + WHERE created_at > '#{date_back_to.getutc}' + UNION + SELECT id, 'PublicBodyVersion', + updated_at AS timestamp + FROM #{PublicBody.versioned_class.table_name} + WHERE updated_at > '#{date_back_to.getutc}' + ORDER by timestamp desc") + @events = WillPaginate::Collection.create((params[:page] or 1), 100) do |pager| + # create a hash for each model type being returned + info_request_event_ids = {} + public_body_version_ids = {} + # get the relevant slice from the paginator + timestamps.slice(pager.offset, pager.per_page).each_with_index do |event, index| + # for each event in the slice, add an item to the hash for the model type + # whose key is the model id, and value is the position in the slice + if event[1] == 'InfoRequestEvent' + info_request_event_ids[event[0].to_i] = index + else + public_body_version_ids[event[0].to_i] = index + end + end + # get all the models in the slice, eagerly loading the associations we use in the view + public_body_versions = PublicBody.versioned_class.find(:all, + :conditions => ['id in (?)', public_body_version_ids.keys], + :include => [ { :public_body => :translations }]) + info_request_events = InfoRequestEvent.find(:all, + :conditions => ['id in (?)', info_request_event_ids.keys], + :include => [:info_request]) + @events = [] + # drop the models into a combined array, ordered by their position in the timestamp slice + public_body_versions.each do |version| + @events[public_body_version_ids[version.id]] = [version, version.updated_at] + end + info_request_events.each do |event| + @events[info_request_event_ids[event.id]] = [event, event.created_at] + end + + # inject the result array into the paginated collection: + pager.replace(@events) + + # set the total entries for the page to the overall number of results + pager.total_entries = timestamps.size end - @events += @public_body_history - @events.sort! { |a,b| b.created_at <=> a.created_at } end def stats diff --git a/app/controllers/admin_request_controller.rb b/app/controllers/admin_request_controller.rb index c5abf8769..7cf23e61e 100644 --- a/app/controllers/admin_request_controller.rb +++ b/app/controllers/admin_request_controller.rb @@ -16,12 +16,25 @@ class AdminRequestController < AdminController def list @query = params[:query] - @info_requests = InfoRequest.paginate :order => "created_at desc", :page => params[:page], :per_page => 100, + @info_requests = InfoRequest.paginate :order => "created_at desc", + :page => params[:page], + :per_page => 100, :conditions => @query.nil? ? nil : ["lower(title) like lower('%'||?||'%')", @query] end def list_old_unclassified - @info_requests = InfoRequest.find_old_unclassified(:conditions => ["prominence = 'normal'"]) + @info_requests = WillPaginate::Collection.create((params[:page] or 1), 50) do |pager| + info_requests = InfoRequest.find_old_unclassified(:conditions => ["prominence = 'normal'"], + :limit => pager.per_page, + :offset => pager.offset) + # inject the result array into the paginated collection: + pager.replace(info_requests) + + unless pager.total_entries + # the pager didn't manage to guess the total count, do it manually + pager.total_entries = InfoRequest.count_old_unclassified(:conditions => ["prominence = 'normal'"]) + end + end end def show diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 6c98ebeba..409a432eb 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -167,17 +167,41 @@ class ApiController < ApplicationController feed_type = params[:feed_type] raise PermissionDenied.new("#{@public_body.id} != #{params[:id]}") if @public_body.id != params[:id].to_i - @events = InfoRequestEvent.find_by_sql([ - %(select info_request_events.* - from info_requests - join info_request_events on info_requests.id = info_request_events.info_request_id - where info_requests.public_body_id = ? - and info_request_events.event_type in ( - 'sent', 'followup_sent', 'resent', 'followup_resent' - ) - order by info_request_events.created_at desc - ), @public_body.id - ]) + since_date_str = params[:since_date] + if since_date_str.nil? + @events = InfoRequestEvent.find_by_sql([ + %(select info_request_events.* + from info_requests + join info_request_events on info_requests.id = info_request_events.info_request_id + where info_requests.public_body_id = ? + and info_request_events.event_type in ( + 'sent', 'followup_sent', 'resent', 'followup_resent' + ) + order by info_request_events.created_at desc + ), @public_body.id + ]) + else + begin + since_date = Date.strptime(since_date_str, "%Y-%m-%d") + rescue ArgumentError + render :json => {"errors" => [ + "Parameter since_date must be in format yyyy-mm-dd (not '#{since_date_str}')" ] }, + :status => 500 + return + end + @events = InfoRequestEvent.find_by_sql([ + %(select info_request_events.* + from info_requests + join info_request_events on info_requests.id = info_request_events.info_request_id + where info_requests.public_body_id = ? + and info_request_events.event_type in ( + 'sent', 'followup_sent', 'resent', 'followup_resent' + ) + and info_request_events.created_at >= ? + order by info_request_events.created_at desc + ), @public_body.id, since_date + ]) + end if feed_type == "atom" render :template => "api/request_events.atom", :layout => false elsif feed_type == "json" diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb index 95d936e54..b8ea82a66 100644 --- a/app/controllers/public_body_controller.rb +++ b/app/controllers/public_body_controller.rb @@ -7,7 +7,7 @@ # # $Id: public_body_controller.rb,v 1.8 2009-09-14 13:27:00 francis Exp $ -require 'csv' +require 'fastercsv' class PublicBodyController < ApplicationController # XXX tidy this up with better error messages, and a more standard infrastructure for the redirect to canonical URL @@ -148,10 +148,10 @@ class PublicBodyController < ApplicationController end def list_all_csv - public_bodies = PublicBody.find(:all, :order => 'url_name') - report = StringIO.new - CSV::Writer.generate(report, ',') do |title| - title << [ + public_bodies = PublicBody.find(:all, :order => 'url_name', + :include => [:translations, :tags]) + report = FasterCSV.generate() do |csv| + csv << [ 'Name', 'Short name', # deliberately not including 'Request email' @@ -164,7 +164,7 @@ class PublicBodyController < ApplicationController 'Version', ] public_bodies.each do |public_body| - title << [ + csv << [ public_body.name, public_body.short_name, # DO NOT include request_email (we don't want to make it @@ -179,8 +179,7 @@ class PublicBodyController < ApplicationController ] end end - report.rewind - send_data(report.read, :type=> 'text/csv; charset=utf-8; header=present', + send_data(report, :type=> 'text/csv; charset=utf-8; header=present', :filename => 'all-authorities.csv', :disposition =>'attachment', :encoding => 'utf8') end diff --git a/app/controllers/request_controller.rb b/app/controllers/request_controller.rb index 6e983a014..268ecc73a 100644 --- a/app/controllers/request_controller.rb +++ b/app/controllers/request_controller.rb @@ -422,19 +422,19 @@ class RequestController < ApplicationController old_described_state = @info_request.described_state @info_request.set_described_state(params[:incoming_message][:described_state]) - # If you're not the *actual* requester owner. e.g. you are playing the + # If you're not the *actual* requester. e.g. you are playing the # classification game, or you're doing this just because you are an # admin user (not because you also own the request). if !@info_request.is_actual_owning_user?(authenticated_user) - # Log what you did, for classification game score purposes. We - # don't log if you were the requester XXX This is presumably so you - # don't score for classifying your own requests. Could instead - # always log and filter at display time. - @info_request.log_event("status_update", + # Log the status change by someone other than the requester + event = @info_request.log_event("status_update", { :user_id => authenticated_user.id, :old_described_state => old_described_state, :described_state => @info_request.described_state, }) + # Create a classification event for league tables + RequestClassification.create!(:user_id => authenticated_user.id, + :info_request_event_id => event.id) # Don't give advice on what to do next, as it isn't their request RequestMailer.deliver_old_unclassified_updated(@info_request) if !@info_request.is_external? diff --git a/app/controllers/request_game_controller.rb b/app/controllers/request_game_controller.rb index 904c44759..f22652dd1 100644 --- a/app/controllers/request_game_controller.rb +++ b/app/controllers/request_game_controller.rb @@ -11,13 +11,12 @@ class RequestGameController < ApplicationController def play session[:request_game] = Time.now - old = InfoRequest.find_old_unclassified(:conditions => ["prominence = 'normal'"]) - @missing = old.size + @missing = InfoRequest.count_old_unclassified(:conditions => ["prominence = 'normal'"]) @total = InfoRequest.count @done = @total - @missing @percentage = (@done.to_f / @total.to_f * 10000).round / 100.0 - @requests = old.sort_by{ rand }.slice(0..2) + @requests = InfoRequest.get_random_old_unclassified(3) if @missing == 0 flash[:notice] = _('<p>All done! Thank you very much for your help.</p><p>There are <a href="{{helpus_url}}">more things you can do</a> to help {{site_name}}.</p>', @@ -25,12 +24,8 @@ class RequestGameController < ApplicationController :site_name => site_name) end - @league_table_28_days = InfoRequestEvent.make_league_table( - [ "event_type = 'status_update' and created_at >= ?", Time.now() - 28.days ] - )[0..10] - @league_table_all_time = InfoRequestEvent.make_league_table( - [ "event_type = 'status_update'"] - )[0..10] + @league_table_28_days = RequestClassification.league_table(10, [ "created_at >= ?", Time.now() - 28.days ]) + @league_table_all_time = RequestClassification.league_table(10) @play_urls = true end diff --git a/app/models/info_request.rb b/app/models/info_request.rb index 6f472c290..2e16d0f58 100644 --- a/app/models/info_request.rb +++ b/app/models/info_request.rb @@ -35,7 +35,7 @@ class InfoRequest < ActiveRecord::Base belongs_to :user validate :must_be_internal_or_external - belongs_to :public_body + belongs_to :public_body, :counter_cache => true validates_presence_of :public_body_id has_many :outgoing_messages, :order => 'created_at' @@ -223,7 +223,7 @@ class InfoRequest < ActiveRecord::Base incoming_message.clear_in_database_caches! end end - + # For debugging def InfoRequest.profile_search(query) t = Time.now.usec @@ -939,26 +939,54 @@ public # Used to find when event last changed def InfoRequest.last_event_time_clause(event_type=nil) event_type_clause = '' - event_type_clause = " and info_request_events.event_type = '#{event_type}'" if event_type - "(select created_at from info_request_events where info_request_events.info_request_id = info_requests.id#{event_type_clause} order by created_at desc limit 1)" + event_type_clause = " AND info_request_events.event_type = '#{event_type}'" if event_type + "(SELECT created_at + FROM info_request_events + WHERE info_request_events.info_request_id = info_requests.id + #{event_type_clause} + ORDER BY created_at desc + LIMIT 1)" end - def InfoRequest.find_old_unclassified(extra_params={}) + def InfoRequest.old_unclassified_params(extra_params, include_last_response_time=false) last_response_created_at = last_event_time_clause('response') age = extra_params[:age_in_days] ? extra_params[:age_in_days].days : OLD_AGE_IN_DAYS - params = {:select => "*, #{last_response_created_at} as last_response_time", - :conditions => ["awaiting_description = ? and #{last_response_created_at} < ? and url_title != 'holding_pen' and user_id is not null", - true, Time.now() - age], - :order => "last_response_time"} - params[:limit] = extra_params[:limit] if extra_params[:limit] - params[:include] = extra_params[:include] if extra_params[:include] + params = { :conditions => ["awaiting_description = ? + AND #{last_response_created_at} < ? + AND url_title != 'holding_pen' + AND user_id IS NOT NULL", + true, Time.now() - age] } + if include_last_response_time + params[:select] = "*, #{last_response_created_at} AS last_response_time" + params[:order] = 'last_response_time' + end + return params + end + + def InfoRequest.count_old_unclassified(extra_params={}) + params = old_unclassified_params(extra_params) + count(:all, params) + end + + def InfoRequest.get_random_old_unclassified(limit) + params = old_unclassified_params({}) + params[:limit] = limit + params[:order] = "random()" + find(:all, params) + end + + def InfoRequest.find_old_unclassified(extra_params={}) + params = old_unclassified_params(extra_params, include_last_response_time=true) + [:limit, :include, :offset].each do |extra| + params[extra] = extra_params[extra] if extra_params[extra] + end if extra_params[:order] params[:order] = extra_params[:order] params.delete(:select) end if extra_params[:conditions] condition_string = extra_params[:conditions].shift - params[:conditions][0] += " and #{condition_string}" + params[:conditions][0] += " AND #{condition_string}" params[:conditions] += extra_params[:conditions] end find(:all, params) diff --git a/app/models/info_request_event.rb b/app/models/info_request_event.rb index a827d19a4..54d2f5ef7 100644 --- a/app/models/info_request_event.rb +++ b/app/models/info_request_event.rb @@ -43,7 +43,7 @@ class InfoRequestEvent < ActiveRecord::Base 'resent', 'followup_sent', 'followup_resent', - + 'edit', # title etc. edited (in admin interface) 'edit_outgoing', # outgoing message edited (in admin interface) 'edit_comment', # comment edited (in admin interface) @@ -53,7 +53,7 @@ class InfoRequestEvent < ActiveRecord::Base 'move_request', # changed user or public body (in admin interface) 'hide', # hid a request (in admin interface) 'manual', # you did something in the db by hand - + 'response', 'comment', 'status_update' @@ -389,24 +389,6 @@ class InfoRequestEvent < ActiveRecord::Base return TMail::Address.parse(prev_addr).address == TMail::Address.parse(curr_addr).address end - # Given a find condition clause, creates a league table of users who made those events. - # XXX this isn't very generic yet, it is just used for the categorisation game tables. - def InfoRequestEvent.make_league_table(conditions) - status_update_events = InfoRequestEvent.find(:all, :conditions => conditions) - table = Hash.new { |h,k| h[k] = 0 } - for event in status_update_events - user_id = event.params[:user_id] - table[user_id] += 1 - end - league_table = [] - for user_id, count in table - user = User.find(user_id) - league_table.push([user, count]) - end - league_table.sort! { |a,b| b[1] <=> a[1] } - return league_table - end - def json_for_api(deep, snippet_highlight_proc = nil) ret = { :id => self.id, diff --git a/app/models/public_body.rb b/app/models/public_body.rb index 60ecb2781..77da81d4c 100644 --- a/app/models/public_body.rb +++ b/app/models/public_body.rb @@ -45,6 +45,8 @@ class PublicBody < ActiveRecord::Base has_many :censor_rules, :order => 'created_at desc' has_tag_string + before_save :set_api_key, :set_default_publication_scheme + translates :name, :short_name, :request_email, :url_name, :notes, :first_letter, :publication_scheme @@ -89,13 +91,13 @@ class PublicBody < ActiveRecord::Base end end - def after_initialize + def set_default_publication_scheme # Make sure publication_scheme gets the correct default value. # (This would work automatically, were publication_scheme not a translated attribute) self.publication_scheme = "" if self.publication_scheme.nil? end - def before_save + def set_api_key self.api_key = SecureRandom.base64(33) if self.api_key.nil? end @@ -104,7 +106,7 @@ class PublicBody < ActiveRecord::Base locale = self.locale || I18n.locale PublicBody.with_locale(locale) do found = PublicBody.find(:all, - :conditions => ["public_body_translations.url_name='#{name}'"], + :conditions => ["public_body_translations.url_name=?", name], :joins => :translations, :readonly => false) # If many bodies are found (usually because the url_name is the same across @@ -184,7 +186,7 @@ class PublicBody < ActiveRecord::Base end acts_as_versioned - self.non_versioned_columns << 'created_at' << 'updated_at' << 'first_letter' << 'api_key' + self.non_versioned_columns << 'created_at' << 'updated_at' << 'first_letter' << 'api_key' << 'info_requests_count' class Version attr_accessor :created_at @@ -549,9 +551,10 @@ class PublicBody < ActiveRecord::Base def notes_as_html self.notes end + def notes_without_html # assume notes are reasonably behaved HTML, so just use simple regexp on this - self.notes.nil? ? '' : self.notes.gsub(/<\/?[^>]*>/, "") + @notes_without_html ||= (self.notes.nil? ? '' : self.notes.gsub(/<\/?[^>]*>/, "")) end def json_for_api diff --git a/app/models/request_classification.rb b/app/models/request_classification.rb new file mode 100644 index 000000000..678b6cd16 --- /dev/null +++ b/app/models/request_classification.rb @@ -0,0 +1,16 @@ +class RequestClassification < ActiveRecord::Base + belongs_to :user + + # return classification instances representing the top n + # users, with a 'cnt' attribute representing the number + # of classifications the user has made. + def RequestClassification.league_table(size, conditions=[]) + find(:all, :select => 'user_id, count(*) as cnt', + :conditions => conditions, + :group => 'user_id', + :order => 'cnt desc', + :limit => size, + :include => :user) + end + +end
\ No newline at end of file diff --git a/app/views/admin_general/timeline.rhtml b/app/views/admin_general/timeline.rhtml index eecab4823..e84539970 100644 --- a/app/views/admin_general/timeline.rhtml +++ b/app/views/admin_general/timeline.rhtml @@ -11,25 +11,25 @@ | <a href="?all=1">All time</a></p> <% last_date = nil %> -<% for event in @events %> - <% if last_date != event.created_at.to_date %> +<% for event, event_at in @events %> + <% if last_date != event_at.to_date %> <% if last_date.nil? %> <p> <% end %> - <h3><%= simple_date(event.created_at) %></h3> + <h3><%= simple_date(event_at) %></h3> <p> <% else %> <br> <% end %> - <% last_date = event.created_at.to_date %> - - <%= simple_time(event.created_at) %> + <% last_date = event_at.to_date %> - <% if event.class.to_s == 'InfoRequestEvent' %> + <%= simple_time(event_at) %> + + <% if event.is_a? InfoRequestEvent %> <%= request_both_links(event.info_request) %> <% if event.event_type == 'edit' %> was edited by administrator <strong><%=h event.params[:editor] %></strong>. - <% for p in ['title', 'prominence', 'described_state', 'awaiting_description'] + <% for p in ['title', 'prominence', 'described_state', 'awaiting_description'] if event.params[p.to_sym] != event.params[('old_'+p).to_sym] %> Changed <%=p%> from '<%=h event.params[('old_'+p).to_sym]%>' to '<%=h event.params[p.to_sym] %>'. <% end @@ -39,7 +39,7 @@ <% outgoing_message = OutgoingMessage.find(event.params[:outgoing_message_id].to_i) %> had outgoing message edited by administrator <strong><%=h event.params[:editor] %></strong>. <% if outgoing_message %> - <% for p in ['body'] + <% for p in ['body'] if event.params[p.to_sym] != event.params[('old_'+p).to_sym] %> Changed <%=p%> from '<%=h event.params[('old_'+p).to_sym]%>' to '<%=h event.params[p.to_sym] %>'. <% end @@ -52,7 +52,7 @@ <% comment = Comment.find(event.params[:comment_id].to_i) %> had annotation edited by administrator <strong><%=h event.params[:editor] %></strong>. <% if comment %> - <% for p in ['body'] + <% for p in ['body'] if event.params[p.to_sym] != event.params[('old_'+p).to_sym] %> Changed <%=p%> from '<%=h event.params[('old_'+p).to_sym]%>' to '<%=h event.params[p.to_sym] %>'. <% end @@ -71,7 +71,7 @@ had incoming message redelivered to another request by administrator <strong><%=h event.params[:editor] %></strong>. <% elsif event.event_type == 'response' %> <% incoming_message = event.incoming_message %> - received + received <%= link_to 'a response', main_url(incoming_message_url(incoming_message)) %> from <%=h event.info_request.public_body.name %>. <% elsif event.event_type == 'sent' %> @@ -95,5 +95,5 @@ <% if not @events.empty? %> </p> <% end %> - +<%= will_paginate(@events) %> diff --git a/app/views/admin_request/list_old_unclassified.rhtml b/app/views/admin_request/list_old_unclassified.rhtml index f42ed0d43..2e75c2174 100644 --- a/app/views/admin_request/list_old_unclassified.rhtml +++ b/app/views/admin_request/list_old_unclassified.rhtml @@ -6,10 +6,11 @@ <ul> <% for @request in @info_requests %> <li> - <%= request_both_links(@request) %> + <%= request_both_links(@request) %> – <%=simple_date(@request.get_last_response_event.created_at)%> </li> <% end %> </ul> +<%= will_paginate(@info_requests) %> diff --git a/app/views/api/request_events.atom.builder b/app/views/api/request_events.atom.builder index 4f0133051..44759ae7e 100644 --- a/app/views/api/request_events.atom.builder +++ b/app/views/api/request_events.atom.builder @@ -6,7 +6,7 @@ atom_feed("xmlns:alaveteli" => "http://www.alaveteli.org/API/v2/RequestEvents/At feed.entry(event) do |entry| request = event.info_request - entry.published(event.created_at) + entry.updated(event.created_at.utc.iso8601) entry.tag!("alaveteli:event_type", event.event_type) entry.tag!("alaveteli:request_url", main_url(request_url(request))) entry.title(request.title) diff --git a/app/views/request_game/play.rhtml b/app/views/request_game/play.rhtml index acf6cce87..eedf19ca2 100644 --- a/app/views/request_game/play.rhtml +++ b/app/views/request_game/play.rhtml @@ -7,22 +7,22 @@ </p> <h2>Top recent players</h2> <table> - <% c = 0; for user, count in @league_table_28_days %> + <% c = 0; for classifications in @league_table_28_days %> <tr> <td> <%= c += 1 %>. <td> - <td> <%= user_link(user) %> </td> - <td> <%=pluralize(count, 'request').gsub(" ", " ")%> </td> + <td> <%= user_link(classifications.user) %> </td> + <td> <%=pluralize(classifications.cnt, 'request').gsub(" ", " ")%> </td> </tr> <% end %> </table> <h2>All time best players</h2> <table> - <% c = 0; for user, count in @league_table_all_time %> + <% c = 0; for classifications in @league_table_all_time %> <tr> <td> <%= c += 1 %>. <td> - <td> <%= user_link(user) %> </td> - <td> <%=pluralize(count, 'request').gsub(" ", " ")%> </td> + <td> <%= user_link(classifications.user) %> </td> + <td> <%= pluralize(classifications.cnt, 'request').gsub(" ", " ")%> </td> </tr> <% end %> </table> |