Author: assaf
Date: Thu Jun 5 16:26:42 2008
New Revision: 663778
URL: http://svn.apache.org/viewvc?rev=663778&view=rev
Log:
Specced Task::Rendering, changed form_completing field to integrated_ui,
use_description? no longer used.
Passing task_url to perform/details frame only when integrated_ui is true.
Task over_due? now returns true or false.
Modified:
ode/sandbox/singleshot/app/helpers/task_helper.rb
ode/sandbox/singleshot/app/models/task.rb
ode/sandbox/singleshot/app/views/tasks/show.html.erb
ode/sandbox/singleshot/db/migrate/20080506015046_create_tasks.rb
ode/sandbox/singleshot/db/schema.rb
ode/sandbox/singleshot/lib/tasks/populate.rake
ode/sandbox/singleshot/public/stylesheets/default.css
ode/sandbox/singleshot/spec/models/task_spec.rb
Modified: ode/sandbox/singleshot/app/helpers/task_helper.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/helpers/task_helper.rb?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/helpers/task_helper.rb (original)
+++ ode/sandbox/singleshot/app/helpers/task_helper.rb Thu Jun 5 16:26:42 2008
@@ -26,20 +26,6 @@
end
end
- def task_frame(task, performing)
- state_uri = URI(task_perform_url(task))
- state_uri.user, state_uri.password = '_token',
task.token_for(authenticated)
- params = { 'task_url'=>state_uri.to_s }
- if performing
- uri = URI(task.rendering.perform_url)
- params.update 'complete_url'=>complete_redirect_tasks_url if
task.rendering.completing
- else
- uri = URI(task.rendering.details_url)
- end
- uri.query = CGI.parse(uri.query || '').update(params).to_query
- content_tag 'iframe', '', :id=>'task_frame', :src=>uri.to_s
- end
-
def task_actions(task)
actions = []
actions << button_to('Cancel', task_url(task,
'task[status]'=>'cancelled'), :method=>:put, :title=>'Cancel this task') if
task.can_cancel?(authenticated)
Modified: ode/sandbox/singleshot/app/models/task.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/models/task.rb?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/models/task.rb (original)
+++ ode/sandbox/singleshot/app/models/task.rb Thu Jun 5 16:26:42 2008
@@ -3,22 +3,22 @@
#
# Table name: tasks
#
-# id :integer not null, primary key
-# title :string(255) not null
-# description :string(255) not null
-# priority :integer(1) not null
-# due_on :date
-# status :string(255) not null
-# perform_url :string(255)
-# details_url :string(255)
-# form_completing :boolean
-# outcome_url :string(255)
-# outcome_type :string(255)
-# access_key :string(32)
-# data :text not null
-# version :integer default(0), not null
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# title :string(255) not null
+# description :string(255) not null
+# priority :integer(1) not null
+# due_on :date
+# status :string(255) not null
+# perform_url :string(255)
+# details_url :string(255)
+# integrated_ui :boolean
+# outcome_url :string(255)
+# outcome_type :string(255)
+# access_key :string(32)
+# data :text not null
+# version :integer default(0), not null
+# created_at :datetime
+# updated_at :datetime
#
require 'openssl'
@@ -82,29 +82,27 @@
validate do |task|
# Check state transitions.
from, to = task.status_change
- if case from # States you cannot transition from.
- when 'suspended'
- task.errors.add :status, 'You are not allowed to resume this task.'
unless task.admin?(task.modified_by)
- when 'completed'
- task.errors.add :status, 'Cannot change status of completed task.'
- when 'cancelled'
- task.errors.add :status, 'Cannot change status of cancelled task.'
- end
- else
- case to # or, states you cannot transition to.
- when 'reserved'
- task.errors.add :status, 'Cannot change status to reserved.' unless
from.nil?
- when 'active'
- #task.errors.add :status, "#{task.owner.fullname} is not allowed to
claim this task." unless
- # task.potential_owners.empty? || task.potential_owner?(task.owner)
|| task.admin?(task.owner)
- when 'suspended'
- task.errors.add :status, 'You are not allowed to suspend this task.'
unless task.admin?(task.modified_by)
- when 'completed'
- task.errors.add :status, 'Cannot change to completed from any status
but active.' unless from =='active'
- task.errors.add :status, 'Only owner can complete task.' unless
task.owner && task.modified_by == task.owner && !task.owner_changed?
- when 'cancelled'
- task.errors.add :status, 'You are not allowed to cancel this task.'
unless task.admin?(task.modified_by)
- end
+ case from # States you cannot transition from.
+ when 'suspended'
+ task.errors.add :status, 'You are not allowed to resume this task.'
unless task.admin?(task.modified_by)
+ when 'completed'
+ task.errors.add :status, 'Cannot change status of completed task.'
+ when 'cancelled'
+ task.errors.add :status, 'Cannot change status of cancelled task.'
+ end
+ case to # or, states you cannot transition to.
+ when 'reserved'
+ task.errors.add :status, 'Cannot change status to reserved.' unless
from.nil?
+ when 'active'
+ #task.errors.add :status, "#{task.owner.fullname} is not allowed to
claim this task." unless
+ # task.potential_owners.empty? || task.potential_owner?(task.owner) ||
task.admin?(task.owner)
+ when 'suspended'
+ task.errors.add :status, 'You are not allowed to suspend this task.'
unless task.admin?(task.modified_by)
+ when 'completed'
+ task.errors.add :status, 'Cannot change to completed from any status but
active.' unless from =='active'
+ task.errors.add :status, 'Only owner can complete task.' unless
task.owner && task.modified_by == task.owner && !task.owner_changed?
+ when 'cancelled'
+ task.errors.add :status, 'You are not allowed to cancel this task.'
unless task.admin?(task.modified_by)
end
task.readonly! if !task.status_changed? && (task.completed? ||
task.cancelled?)
end
@@ -113,47 +111,63 @@
# -- View and perform ---
# Some tasks are performed offline, for example, calling a customer. Other
- # tasks are performed online, in which case we would like to include the UI
- # for performing the task as part of the task page.
+ # tasks performed onlined, in which case we would like to render that UI
+ # component as part of the task view.
+ #
+ # There are two possible views for each task. One view, presented to the
+ # task owner for performing the task, the other view presented to everyone
+ # else and only provides details about the task.
#
- # There are two views for each task. One view presented to the task owner
- # for performing the task, the other view, presented to everyone else only
- # provides details about the task.
+ # Some UIs are integrated with the task manager: they obtain the task state
+ # and update it upon completion. Other UIs require that the user mark the
+ # task upon completion.
#
- # Some forms are integrated with the task manager, these know how to update
- # the task status and mark the task as completed. For all other forms, we
- # need to include a button to mark the task as completed.
+ # Tasks that do not have a UI representation (e.g. offline tasks) should use
+ # the task description as the most adequate representation. Calling
+ # #render_url on these tasks returns nil. Tasks that do have a UI
+ # representation should use the URL returned by #render_url, e.g. to pull
+ # that UI into an IFrame.
#
- # We handle these cases through several combinations of rendering
- # information. Some tasks are rendered using only the task description (e.g.
- # offline tasks). Other tasks provide a URL for performing the task, using
- # the description for everyone else. Last, some tasks provide both a URL for
- # performing the task and a URL for viewing task details.
+ # UIs that integrate with the taske manager (#integrated_ui) will need
+ # additional query parameters in the URL, those are passed to render_for
+ # using an argument/block. UIs that are not integrated should provide the
+ # user with other means for marking the task as completed
+ # (#use_completion_button?).
class Rendering
- MAPPING = [%w{perform_url perform_url}, %w{details_url details_url},
%w{form_completing completing}]
- attr_reader :perform_url, :details_url, :completing
+ MAPPING = %w{perform_url details_url integrated_ui}.map { |name| [name,
name] }
+ attr_reader :perform_url, :details_url, :integrated_ui
- def initialize(perform_url, details_url, completing)
+ def initialize(perform_url, details_url, integrated_ui)
@perform_url = perform_url
@details_url = details_url if perform_url
- @completing = perform_url && completing || false
+ @integrated_ui = (perform_url && integrated_ui) || false
end
- # True if rendering the task using only the task description.
- def use_description?(performing)
- performing ? perform_url.nil? : details_url.nil?
+ # True if rendering a button for user to mark task as completed.
+ def use_completion_button?
+ !perform_url || !integrated_ui
end
- # True if we need to include button to mark task as completed.
- def use_completion_button?
- !perform_url || !completing
+ # Returns most suitable URL for rendering the task.
+ #
+ # Returns nil if there is no suitable URL for rendering the task,
+ # otherwise, returns perform_url or details_url. If the integrated_ui
+ # option is available, passes query parameters to the rendered URL. Query
+ # parameters are passed as last argument or returned from the block.
+ def render_url(perform, params = {})
+ url = perform ? perform_url : details_url
+ return url unless integrated_ui
+ params = yield if block_given?
+ uri = URI(url)
+ uri.query = CGI.parse(uri.query || '').update(params).to_query
+ uri.to_s
end
end
composed_of :rendering, :class_name=>Rendering.to_s,
:mapping=>Rendering::MAPPING do |hash|
- Rendering.new(hash[:perform_url], hash[:details_url], hash[:completing])
+ Rendering.new(hash[:perform_url], hash[:details_url], hash[:integrated_ui])
end
validates_url :perform_url, :allow_nil=>true
@@ -288,7 +302,7 @@
end
def over_due?
- (ready? || active?) && due_on && due_on < Date.today
+ due_on ? (ready? || active?) && due_on < Date.today : false
end
# Scopes can use this to add ranking methods on returned records.
Modified: ode/sandbox/singleshot/app/views/tasks/show.html.erb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/app/views/tasks/show.html.erb?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/app/views/tasks/show.html.erb (original)
+++ ode/sandbox/singleshot/app/views/tasks/show.html.erb Thu Jun 5 16:26:42
2008
@@ -1,6 +1,10 @@
<%
performing = @task.can_complete?(authenticated)
- use_description = @task.rendering.use_description?(performing)
+ iframe_url = @task.rendering.render_url(performing) {
+ uri = URI(task_perform_url(@task))
+ uri.user, uri.password = '_token', @task.token_for(authenticated)
+ { 'task_url'=>uri.to_s }
+ }
%>
<% div_for @task do %>
<div class='header'>
@@ -16,7 +20,7 @@
</div>
<div class='details' style='display:none'>
<dl>
- <%= content_tag('dt', 'Description') + content_tag('dd',
sanitize(simple_format(@task.description))) unless use_description %>
+ <%= content_tag('dt', 'Description') + content_tag('dd',
sanitize(simple_format(@task.description))) if iframe_url %>
<dt>Priority</dt><dd><%= ['High', 'Medium', 'Low'[EMAIL PROTECTED] -
1] %></dd>
<%= content_tag('dt', 'Due on') + content_tag('dd',
relative_date(@task.due_on).humanize) if @task.due_on %>
<dt>Recent activity</dt>
@@ -29,10 +33,10 @@
</div>
</div>
- <% if use_description %>
- <div class='description'><%= sanitize(simple_format(@task.description))
%></div>
- <% else %>
- <%= task_frame @task, performing %>
+ <% if iframe_url %>
+ <iframe id='task_frame' src='<%= iframe_url %>'></iframe>
<%= javascript_tag "Singleshot.setFrameSize('task_frame')" %>
+ <% else %>
+ <div class='description'><%= sanitize(simple_format(@task.description))
%></div>
<% end %>
<% end %>
Modified: ode/sandbox/singleshot/db/migrate/20080506015046_create_tasks.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/migrate/20080506015046_create_tasks.rb?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/migrate/20080506015046_create_tasks.rb (original)
+++ ode/sandbox/singleshot/db/migrate/20080506015046_create_tasks.rb Thu Jun 5
16:26:42 2008
@@ -8,7 +8,7 @@
t.string 'status', :null=>false
t.string 'perform_url'
t.string 'details_url'
- t.boolean 'form_completing'
+ t.boolean 'integrated_ui'
t.string 'outcome_url', :null=>true
t.string 'outcome_type', :null=>true
t.string 'access_key', :null=>true, :limit=>32
Modified: ode/sandbox/singleshot/db/schema.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/db/schema.rb?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/db/schema.rb (original)
+++ ode/sandbox/singleshot/db/schema.rb Thu Jun 5 16:26:42 2008
@@ -43,19 +43,19 @@
end
create_table "tasks", :force => true do |t|
- t.string "title", :null => false
- t.string "description", :null => false
- t.integer "priority", :limit => 1, :null => false
+ t.string "title", :null => false
+ t.string "description", :null => false
+ t.integer "priority", :limit => 1, :null => false
t.date "due_on"
- t.string "status", :null => false
+ t.string "status", :null => false
t.string "perform_url"
t.string "details_url"
- t.boolean "form_completing"
+ t.boolean "integrated_ui"
t.string "outcome_url"
t.string "outcome_type"
- t.string "access_key", :limit => 32
- t.text "data", :null => false
- t.integer "version", :default => 0, :null => false
+ t.string "access_key", :limit => 32
+ t.text "data", :null => false
+ t.integer "version", :default => 0, :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
Modified: ode/sandbox/singleshot/lib/tasks/populate.rake
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/lib/tasks/populate.rake?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/lib/tasks/populate.rake (original)
+++ ode/sandbox/singleshot/lib/tasks/populate.rake Thu Jun 5 16:26:42 2008
@@ -35,7 +35,7 @@
Task.delay
you = Person.find_by_identity(ENV['USER'])
defaults = { :title=>Faker::Lorem.sentence,
:description=>Faker::Lorem.paragraphs(3).join("\n\n"),
- :rendering=>{
:perform_url=>'http://localhost:3001/sandwich', :completing=>true },
:potential_owners=>[you, other] }
+ :rendering=>{
:perform_url=>'http://localhost:3001/sandwich', :integrated_ui=>true },
:potential_owners=>[you, other] }
returning Task.new(defaults.merge(attributes || {})) do |task|
task.modify_by(you).save!
def task.delay(duration = 2.hours)
Modified: ode/sandbox/singleshot/public/stylesheets/default.css
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/public/stylesheets/default.css?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/public/stylesheets/default.css (original)
+++ ode/sandbox/singleshot/public/stylesheets/default.css Thu Jun 5 16:26:42
2008
@@ -54,7 +54,7 @@
}
form.button-to input, a.button-to, button {
font-size: 1em;
- font-weight: bold;
+ font-weight: normal;
border: none;
padding: 0.3em 0.6em 0.3em 0.6em;
margin: 0;
@@ -109,10 +109,11 @@
border-bottom: solid 2px #055da4;
}
#header h1 {
- font-size: 2.2em;
+ font-size: 2.0em;
font-weight: bold;
- margin: 0 auto 0.7em auto;
+ margin: 0 auto 0.6em auto;
float: left;
+ letter-spacing: -1pt;
}
#header h1 a {
color: #404040;
@@ -125,8 +126,6 @@
}
#header ul.links li {
display: inline;
- margin: 0;
- padding: 0.5em;
}
#header ul.links li a {
text-decoration: none;
@@ -146,20 +145,19 @@
}
#header ul.tabs li a {
color: #eee;
- font-size: 1.2em;
- font-weight: bold;
+ font-size: 1.1em;
text-decoration: none;
background-color: #0081d1;
padding: 0.3em 0.9em 0.3em 0.9em;
-moz-border-radius: 4px 4px 0 0;
- -webkit-border-top-left-radius: 6px;
- -webkit-border-top-right-radius: 6px;
+ -webkit-border-top-left-radius: 4px;
+ -webkit-border-top-right-radius: 4px;
}
#header ul.tabs li a:hover, #header ul.tabs li a.current {
background-color: #055da4;
}
#header ul.alternate {
- margin-top: -1.3em;
+ margin-top: -1.5em;
}
@@ -294,10 +292,11 @@
border-bottom: solid 2px #055da4;
}
div.task div.header h1 {
- display: inline;
+ font-size: 2.0em;
+ font-weight: bold;
+ margin: 0.2em 1em 0 1.5em;
float: left;
- font-size: 1.5em;
- margin: 0.6em 2em 0 2.0em;
+ letter-spacing: -1pt;
}
div.task div.header h1 a {
color: #404040;
@@ -307,9 +306,10 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
+ margin: 0.6em auto 0.6em auto;
}
div.task div.header div.vitals p {
- margin: 0.3em 0 0.3em 0;
+ margin: 0 auto 0.3em auto;
text-overflow: ellipsis;
overflow: hidden;
}
@@ -404,6 +404,3 @@
form.login input {
width: 100%;
}
-
-
-
Modified: ode/sandbox/singleshot/spec/models/task_spec.rb
URL:
http://svn.apache.org/viewvc/ode/sandbox/singleshot/spec/models/task_spec.rb?rev=663778&r1=663777&r2=663778&view=diff
==============================================================================
--- ode/sandbox/singleshot/spec/models/task_spec.rb (original)
+++ ode/sandbox/singleshot/spec/models/task_spec.rb Thu Jun 5 16:26:42 2008
@@ -273,19 +273,25 @@
describe 'over_due?' do
it 'should be false if task has no due date' do
- Task.new.over_due?.should be_false
+ Task.create(defaults).over_due?.should be_false
end
it 'should be false if task due date in the future' do
- Task.new(:due_on=>Date.tomorrow).over_due?.should be_false
+ Task.create(defaults(:due_on=>Date.tomorrow)).over_due?.should be_false
end
it 'should be false if task due today' do
- Task.new(:due_on=>Date.today).over_due?.should be_false
+ Task.create(defaults(:due_on=>Date.today)).over_due?.should be_false
end
it 'should be true if task due date in the past' do
- Task.new(:due_on=>Date.yesterday).over_due?.should be_true
+ Task.create(defaults(:due_on=>Date.yesterday)).over_due?.should be_true
+ end
+
+ it 'should be true only if task is ready or active' do
+ for status in Task::STATUSES
+ task_with_status(status, :due_on=>Date.yesterday).over_due?.should ==
(status == 'ready' || status == 'active')
+ end
end
end
@@ -331,6 +337,117 @@
end
+describe Task::Rendering do
+ it 'should store perform_url attribute' do
+ Task.create! defaults(:perform_url=>'http://foobar/')
+ Task.last.rendering.perform_url.should == 'http://foobar/'
+ end
+
+ it 'should store details_url attribute' do
+ Task.create! defaults(:perform_url=>'http://foobar/',
:details_url=>'http://barfoo/')
+ Task.last.rendering.details_url.should == 'http://barfoo/'
+ end
+
+ it 'should not have details_url without perform_url' do
+ Task.create! defaults(:details_url=>'http://barfoo/')
+ Task.last.rendering.details_url.should be_nil
+ end
+
+ it 'should store integrated_ui attribute' do
+ Task.create! defaults(:perform_url=>'http://foobar/', :integrated_ui=>true)
+ Task.last.rendering.integrated_ui.should be_true
+ end
+
+ it 'should not have integrated_ui without perform_url' do
+ Task.create! defaults(:integrated_ui=>true)
+ Task.last.rendering.integrated_ui.should be_false
+ end
+
+ it 'should default integrated_ui attribute to false' do
+ Task.create! defaults(:perform_url=>'http://foobar/')
+ Task.last.rendering.integrated_ui.should be_false
+ end
+
+ it 'should use completion button if no perform_url' do
+ Task.create! defaults
+ Task.last.rendering.use_completion_button?.should be_true
+ end
+
+ it 'should use completion button if perform_url but no integrated_ui' do
+ Task.create! defaults(:perform_url=>'http://foobar/')
+ Task.last.rendering.use_completion_button?.should be_true
+ end
+
+ it 'should not use completion button if perform_url and integrated_ui' do
+ Task.create! defaults(:perform_url=>'http://foobar/', :integrated_ui=>true)
+ Task.last.rendering.use_completion_button?.should be_false
+ end
+
+ it 'should have nil render_url without perform_url' do
+ Task.new.rendering.render_url(true).should be_nil
+ end
+
+ it 'should render using perform_url when performing task' do
+ Task.new(:perform_url=>'http://foobar/',
:details_url=>'http://barfoo/').rendering.render_url(true).should ==
'http://foobar/'
+ end
+
+ it 'should render using perform_url with query parameter when performing
integrated task' do
+ task = Task.new(:perform_url=>'http://foobar/',
:details_url=>'http://barfoo/', :integrated_ui=>true)
+ task.rendering.render_url(true, 'url'=>'http://test.host').should ==
"http://foobar/?url=#{CGI.escape('http://test.host')}"
+ task.rendering.render_url(true) { { 'url'=>'http://test.host' } }.should
== "http://foobar/?url=#{CGI.escape('http://test.host')}"
+ end
+
+ it 'should have nil render_url without details_url' do
+
Task.new(:perform_url=>'http://foobar/').rendering.render_url(false).should
be_nil
+ end
+
+ it 'should render using details_url when performing task' do
+ Task.new(:perform_url=>'http://foobar/',
:details_url=>'http://barfoo/').rendering.render_url(false).should ==
'http://barfoo/'
+ end
+
+ it 'should render using details_url with query parameters when viewing
integrated task' do
+ task = Task.new(:perform_url=>'http://foobar/',
:details_url=>'http://barfoo/', :integrated_ui=>true)
+ task.rendering.render_url(false, 'url'=>'http://test.host' ).should ==
"http://barfoo/?url=#{CGI.escape('http://test.host')}"
+ task.rendering.render_url(false) { { 'url'=>'http://test.host' } }.should
== "http://barfoo/?url=#{CGI.escape('http://test.host')}"
+ end
+
+ it 'should be assignable from hash' do
+ hash = { :perform_url=>'http://foobar/', :details_url=>'http://barfoo/',
:integrated_ui=>true }
+ task = Task.new
+ task.attributes = { :rendering=>hash }
+ hash.each do |key, value|
+ task.rendering.send(key).should == value
+ end
+ end
+
+ it 'should validate URLs' do
+ Task.new(:perform_url=>'http://+++').should have(1).error_on(:perform_url)
+ Task.new(:details_url=>'http://+++').should have(1).error_on(:details_url)
+ end
+
+ it 'should allow HTTP URLS' do
+ Task.new(:perform_url=>'http://test.host/foo').should have(:no).errors
+ Task.new(:details_url=>'http://test.host?foo=bar').should have(:no).errors
+ end
+
+ it 'should allow HTTPS URLS' do
+ Task.new(:perform_url=>'https://test.host/foo').should have(:no).errors
+ Task.new(:details_url=>'https://test.host?foo=bar').should have(:no).errors
+ end
+
+ it 'should not allow other URL schemes' do
+ Task.new(:perform_url=>'ftp://test.host/foo').should
have(1).error_on(:perform_url)
+ Task.new(:details_url=>'file:///var/log').should
have(1).error_on(:details_url)
+ end
+
+ it 'should store normalized URLs' do
+ Task.create defaults(:perform_url=>'HTTP://Test.Host/Foo',
:details_url=>'HTTPS://Foo:[EMAIL PROTECTED]')
+ Task.last.perform_url.should eql('http://test.host/Foo')
+ Task.last.details_url.should eql('https://Foo:[EMAIL PROTECTED]/?Foo=Bar')
+ end
+
+end
+
=begin
describe Task, 'url', :shared=>true do