mysociety / transparency-fnf-whatdotheyknow-projects

Issues repository for the Nesta Future News Fund "WhatDoTheyKnow Projects" project
0 stars 0 forks source link

Technical design & development implementation 3 – Project Queues #28

Closed garethrees closed 4 years ago

garethrees commented 4 years ago

Here's my spike at using an ephemeral session-based queue. There are probably some gotchas here which I haven't thought about, but I wanted to shared direction early so that we can poke holes in the concept before I try to implement it within Alaveteli itself.

I've commented the code to try to explain the thinking behind the main points.

# Abstract queue that's just the common behaviour. We'll only implement
# subclasses within the application
class Project::Queue
  # A Queue is unique per user per project
  def initialize(project, current_user, session)
    @project = project
    @current_user = current_user
    @session = session
  end

  # This is the main method – give me a random, actionable request from the
  # queue, based on what's actionable within the project and that I haven't
  # skipped
  def sample
    unskipped_requests.sample
  end

  def skip(info_request_id)
    skipped_requests << info_request_id
  end

  def clear_skips
    skipped_requests = []
  end

  private

  attr_reader :project
  attr_reader :current_user
  attr_reader :session

  # info_requests gets defined by the subclass
  def unskipped_requests
    info_requests.where.not(id: skipped_requests)
  end

  # TODO: Use Set so we only add if the collection doesn't contain the ID
  # Probably need to set a default empty value?
  def skipped_requests
    session[queue_key]
  end

  # Can probably make this a little neater
  def queue_key
    "project_#{ project.id }_skipped_#{ queue_name }_#{ current_user.id }"
  end

  # Uses the subclass name so that we don't have to implement anything special
  # Project::Queue::Classifiable => 'classifiable'
  def queue_name
    self.class.to_s.demodulize.underscore
  end

  # Requires subclass implementation:

  def info_requests
    raise NotImplementedError
  end
end

# Implement the specific queues by calling a relevant query method on the
# project instance encapsulated by the Queue class.
class Project::Queue::Classifiable < Project::Queue
  private

  def info_requests
    project.classifiable_requests
  end
end

class Project::Queue::Extractable < Project::Queue
  private

  def info_requests
    project.extractable_requests
  end
end

class Projects::ClassifiesController
  def show
    # Build a queue for the current user
    @queue = Project::Queue::Classifiable.new(@project, current_user, session)
    # Grab a random request that hasn't been skiped from the queue
    @info_request = @queue.sample

    # If there are no requests left to classify, clear the skips and go back to
    # the project homepage
    unless @info_request
      @queue.clear_skips
      flash[:notice] = _('No more requests to classify right now!')
      redirect_to project_path(@project)
    end
  end

  def update
    # Append the skipped InfoRequest ID to the skipped requests in the queue
    # and redirect to classify another request
    @queue = Project::Queue::Classifiable.new(@project, current_user, session)
    @queue.skip(params[:info_request_id])
    redirect_to project_classify_path(@project)
  end
end

# Same deal here, just with a different queue
class Projects::ExtractsController
  def show
    @queue = Project::Queue::Extractable.new(@project, current_user, session)
    @info_request = @queue.sample

    unless @info_request
      @queue.clear_skips
      flash[:notice] = _('No more requests to extract data from right now!')
      redirect_to project_path(@project)
    end
  end

  # etc…
end

@gbp what do you think of this approach?

garethrees commented 4 years ago

Here's a patch that hacks in the above spike:

hack-queue-in-classifies-controller.txt

garethrees commented 4 years ago

This was implemented by https://github.com/mysociety/alaveteli/pull/5691, https://github.com/mysociety/alaveteli/pull/5693 and https://github.com/mysociety/alaveteli/pull/5695 so calling it done.

https://github.com/mysociety/alaveteli/pull/5696 cleans up the code, but closing as the capability has been added.