Skip to content
This repository
Browse code

Merge pull request #11 from alphagov/new_product_features_form

New product features form
  • Loading branch information...
commit c196ced2b5d2d569f1009853281cf2d2aef08b4a 2 parents eadaf2f + 4c99e6a
Jamie Cobbett authored November 22, 2012

Showing 40 changed files with 511 additions and 144 deletions. Show diff stats Hide diff stats

  1. 2  Gemfile
  2. 8  Gemfile.lock
  3. 5  app/assets/javascripts/application.js
  4. 2  app/assets/stylesheets/application.css
  5. 4  app/assets/stylesheets/forms.css
  6. 46  app/controllers/application_controller.rb
  7. 2  app/controllers/content_change_requests_controller.rb
  8. 2  app/controllers/create_new_user_requests_controller.rb
  9. 30  app/controllers/general_requests_controller.rb
  10. 15  app/controllers/new_feature_requests_controller.rb
  11. 2  app/controllers/remove_user_requests_controller.rb
  12. 77  app/controllers/requests_controller.rb
  13. 2  app/controllers/support_controller.rb
  14. 15  app/models/general_request.rb
  15. 11  app/models/new_feature_request.rb
  16. 9  app/models/time_constraint.rb
  17. 17  app/models/with_requester.rb
  18. 16  app/models/with_time_constraint.rb
  19. 1  app/views/layouts/application.html.erb
  20. 23  app/views/new_feature_requests/new.html.erb
  21. 16  app/views/support/_requester.html.erb
  22. 7  app/views/support/_time_constraint.html.erb
  23. 40  config/initializers/validates_timeliness.rb
  24. 16  config/locales/validates_timeliness.en.yml
  25. 1  config/routes.rb
  26. 8  features/general_requests.feature
  27. 33  features/new_feature_requests.feature
  28. 19  features/step_definitions/general_request_steps.rb
  29. 53  features/step_definitions/request_steps.rb
  30. 9  features/step_definitions/zendesk_steps.rb
  31. 17  lib/general_request_zendesk_ticket.rb
  32. 32  lib/new_feature_request_zendesk_ticket.rb
  33. 4  lib/zendesk_request.rb
  34. 42  lib/zendesk_ticket.rb
  35. 7  test/unit/models/new_feature_request_test.rb
  36. 35  test/unit/models/time_constraint_test.rb
  37. 27  test/unit/zendesk_ticket_test.rb
  38. 0  vendor/assets/javascripts/.gitkeep
  39. 0  vendor/assets/stylesheets/.gitkeep
  40. 0  vendor/plugins/.gitkeep
2  Gemfile
@@ -14,9 +14,11 @@ gem 'aws-ses', require: 'aws/ses'
14 14
 gem 'exception_notification', '~> 2.4.1', require: 'exception_notifier'
15 15
 gem 'gds-sso', '2.1.0'
16 16
 gem 'jquery-rails'
  17
+gem 'jquery-ui-rails', '2.0.2'
17 18
 gem 'plek', '0.5.0'
18 19
 gem 'zendesk_api', '0.1.2'
19 20
 gem 'formtastic-bootstrap', '2.0.0'
  21
+gem 'validates_timeliness', '3.0.14'
20 22
 
21 23
 group :test do
22 24
   gem "mocha", "0.12.6", require: false
8  Gemfile.lock
@@ -98,6 +98,9 @@ GEM
98 98
     jquery-rails (2.1.3)
99 99
       railties (>= 3.1.0, < 5.0)
100 100
       thor (~> 0.14)
  101
+    jquery-ui-rails (2.0.2)
  102
+      jquery-rails
  103
+      railties (>= 3.1.0)
101 104
     json (1.7.5)
102 105
     jwt (0.1.5)
103 106
       multi_json (>= 1.0)
@@ -192,6 +195,7 @@ GEM
192 195
       libv8 (~> 3.3.10)
193 196
     thor (0.16.0)
194 197
     tilt (1.3.3)
  198
+    timeliness (0.3.7)
195 199
     treetop (1.4.11)
196 200
       polyglot
197 201
       polyglot (>= 0.3.1)
@@ -203,6 +207,8 @@ GEM
203 207
       kgio (~> 2.6)
204 208
       rack
205 209
       raindrops (~> 0.7)
  210
+    validates_timeliness (3.0.14)
  211
+      timeliness (~> 0.3.6)
206 212
     warden (1.2.1)
207 213
       rack (>= 1.0)
208 214
     webmock (1.8.11)
@@ -232,6 +238,7 @@ DEPENDENCIES
232 238
   formtastic-bootstrap (= 2.0.0)
233 239
   gds-sso (= 2.1.0)
234 240
   jquery-rails
  241
+  jquery-ui-rails (= 2.0.2)
235 242
   mocha (= 0.12.6)
236 243
   plek (= 0.5.0)
237 244
   poltergeist (= 0.7.0)
@@ -241,5 +248,6 @@ DEPENDENCIES
241 248
   therubyracer (~> 0.9.4)
242 249
   uglifier (>= 1.0.3)
243 250
   unicorn (= 4.3.1)
  251
+  validates_timeliness (= 3.0.14)
244 252
   webmock (= 1.8.11)
245 253
   zendesk_api (= 0.1.2)
5  app/assets/javascripts/application.js
@@ -12,4 +12,9 @@
12 12
 //
13 13
 //= require jquery
14 14
 //= require jquery_ujs
  15
+//= require jquery.ui.datepicker
15 16
 //= require_tree .
  17
+
  18
+$(document).ready(function() {
  19
+  $('input[calendar-enabled=true]').datepicker({minDate: 0, dateFormat: 'dd-mm-yy'});
  20
+});
2  app/assets/stylesheets/application.css
@@ -11,4 +11,6 @@
11 11
  *= require_self
12 12
  *= require_tree .
13 13
  *= require formtastic-bootstrap
  14
+ *= require jquery.ui.datepicker
  15
+ *= require forms
14 16
  */
4  app/assets/stylesheets/forms.css
... ...
@@ -0,0 +1,4 @@
  1
+legend {
  2
+  font-size: 1.2em;
  3
+  font-weight: bold;
  4
+}
46  app/controllers/application_controller.rb
... ...
@@ -1,53 +1,7 @@
1  
-require "zendesk_request"
2  
-require "zendesk_client"
3  
-
4 1
 class ApplicationController < ActionController::Base
5 2
   include GDS::SSO::ControllerMethods
6 3
   
7 4
   before_filter :authenticate_user!
8 5
   
9 6
   protect_from_forgery
10  
-
11  
-  private
12  
-
13  
-  def on_get(template)
14  
-    load_client_and_organisations("zendesk_error_upon_new_form")
15  
-
16  
-    @formdata = {}
17  
-    render :"#{template}", :layout => "application"
18  
-  end
19  
-
20  
-  def on_post(params, route)
21  
-    load_client_and_organisations("zendesk_error_upon_submit")
22  
-    @formdata = params
23  
-
24  
-    if @errors.empty?
25  
-      ticket = ZendeskRequest.raise_zendesk_request(@client, params, route)
26  
-      if ticket
27  
-        redirect_to '/acknowledge'
28  
-      else
29  
-        return render :"support/zendesk_error", :locals => {:error_string => "zendesk_error_upon_submit"}
30  
-      end
31  
-    else
32  
-      render :"#{@template}", :layout => "application", :status => 400
33  
-    end
34  
-  end
35  
-
36  
-  def load_client_and_organisations(error_string)
37  
-    begin
38  
-      @client = ZendeskClient.get_client(logger)
39  
-      @organisations = ZendeskRequest.get_organisations(@client)
40  
-    rescue ZendeskError
41  
-      return render :"support/zendesk_error", :locals => {:error_string => error_string}
42  
-    end
43  
-  end
44  
-
45  
-  def prepopulate_organisation_list
46  
-    begin
47  
-      @client = ZendeskClient.get_client(logger)
48  
-      @organisations = ZendeskRequest.get_organisations(@client)
49  
-    rescue ZendeskError
50  
-      return render :"support/zendesk_error",  :locals => {:error_string => "zendesk_error_upon_new_form"}
51  
-    end
52  
-  end
53 7
 end
2  app/controllers/content_change_requests_controller.rb
... ...
@@ -1,6 +1,6 @@
1 1
 require 'guard'
2 2
 
3  
-class ContentChangeRequestsController < ApplicationController
  3
+class ContentChangeRequestsController < RequestsController
4 4
   def new
5 5
     @formdata = {}
6 6
     prepopulate_organisation_list
2  app/controllers/create_new_user_requests_controller.rb
... ...
@@ -1,4 +1,4 @@
1  
-class CreateNewUserRequestsController < ApplicationController
  1
+class CreateNewUserRequestsController < RequestsController
2 2
   def new
3 3
     @formdata = {}
4 4
     prepopulate_organisation_list
30  app/controllers/general_requests_controller.rb
... ...
@@ -1,26 +1,18 @@
1 1
 require 'general_request_zendesk_ticket'
2 2
 
3  
-class GeneralRequestsController <  ApplicationController
4  
-  def new
5  
-    @request = GeneralRequest.new(:requester => Requester.new)
6  
-    prepopulate_organisation_list
7  
-  end
  3
+class GeneralRequestsController <  RequestsController
8 4
 
9  
-  def create
10  
-    @request = GeneralRequest.new(params[:general_request])
11  
-    @request.user_agent = request.user_agent
  5
+  def new_request
  6
+    GeneralRequest.new(:requester => Requester.new)
  7
+  end
12 8
 
13  
-    load_client_and_organisations("zendesk_error_upon_submit")
  9
+  def zendesk_ticket_class
  10
+    GeneralRequestZendeskTicket
  11
+  end
14 12
 
15  
-    if @request.valid?
16  
-      ticket = ZendeskRequest.raise_ticket(@client, GeneralRequestZendeskTicket.new(@request))
17  
-      if ticket
18  
-        redirect_to acknowledge_path
19  
-      else
20  
-        return render "support/zendesk_error", :locals => {:error_string => "zendesk_error_upon_submit"}
21  
-      end
22  
-    else
23  
-      render :new, :status => 400
24  
-    end
  13
+  def parse_request_from_params
  14
+    user_request = GeneralRequest.new(params[:general_request])
  15
+    user_request.user_agent = request.user_agent
  16
+    user_request
25 17
   end
26 18
 end
15  app/controllers/new_feature_requests_controller.rb
... ...
@@ -0,0 +1,15 @@
  1
+require 'new_feature_request_zendesk_ticket'
  2
+
  3
+class NewFeatureRequestsController < RequestsController
  4
+  def new_request
  5
+    NewFeatureRequest.new(:requester => Requester.new, :time_constraint => TimeConstraint.new)
  6
+  end
  7
+
  8
+  def zendesk_ticket_class
  9
+    NewFeatureRequestZendeskTicket
  10
+  end
  11
+
  12
+  def parse_request_from_params
  13
+    NewFeatureRequest.new(params[:new_feature_request])
  14
+  end
  15
+end
2  app/controllers/remove_user_requests_controller.rb
... ...
@@ -1,6 +1,6 @@
1 1
 require 'guard'
2 2
 
3  
-class RemoveUserRequestsController < ApplicationController
  3
+class RemoveUserRequestsController < RequestsController
4 4
   def new
5 5
     @formdata = {}
6 6
     prepopulate_organisation_list
77  app/controllers/requests_controller.rb
... ...
@@ -0,0 +1,77 @@
  1
+require "zendesk_request"
  2
+require "zendesk_client"
  3
+
  4
+class RequestsController < ApplicationController
  5
+  def new
  6
+    @request = new_request
  7
+    prepopulate_organisation_list
  8
+  end
  9
+
  10
+  def create
  11
+    @request = parse_request_from_params
  12
+    if @request.valid?
  13
+      raise_ticket(zendesk_ticket_class.new(@request))
  14
+    else
  15
+      prepopulate_organisation_list
  16
+      render :new, :status => 400
  17
+    end
  18
+  end
  19
+
  20
+  private
  21
+
  22
+  def on_get(template)
  23
+    load_client_and_organisations("zendesk_error_upon_new_form")
  24
+
  25
+    @formdata = {}
  26
+    render :"#{template}", :layout => "application"
  27
+  end
  28
+
  29
+  def on_post(params, route)
  30
+    load_client_and_organisations("zendesk_error_upon_submit")
  31
+    @formdata = params
  32
+
  33
+    if @errors.empty?
  34
+      ticket = ZendeskRequest.raise_zendesk_request(@client, params, route)
  35
+      if ticket
  36
+        redirect_to '/acknowledge'
  37
+      else
  38
+        return render :"support/zendesk_error", :locals => {:error_string => "zendesk_error_upon_submit"}
  39
+      end
  40
+    else
  41
+      render :"#{@template}", :layout => "application", :status => 400
  42
+    end
  43
+  end
  44
+
  45
+  def raise_ticket(ticket)
  46
+    load_client
  47
+
  48
+    ticket = ZendeskRequest.raise_ticket(@client, ticket)
  49
+
  50
+    if ticket
  51
+      redirect_to acknowledge_path
  52
+    else
  53
+      return render "support/zendesk_error", :locals => {:error_string => "zendesk_error_upon_submit"}
  54
+    end
  55
+  end
  56
+
  57
+  def load_client_and_organisations(error_string)
  58
+    load_client
  59
+    load_organisations(error_string)
  60
+  end
  61
+
  62
+  def load_client
  63
+    @client = ZendeskClient.get_client(logger)
  64
+  end
  65
+
  66
+  def load_organisations(error_string)
  67
+    begin
  68
+      @organisations = ZendeskRequest.get_organisations(@client)
  69
+    rescue ZendeskError
  70
+      return render :"support/zendesk_error", :locals => {:error_string => error_string}
  71
+    end
  72
+  end
  73
+
  74
+  def prepopulate_organisation_list
  75
+    load_client_and_organisations("zendesk_error_upon_new_form")
  76
+  end
  77
+end
2  app/controllers/support_controller.rb
... ...
@@ -1,6 +1,6 @@
1 1
 require "guard"
2 2
 
3  
-class SupportController < ApplicationController
  3
+class SupportController < RequestsController
4 4
   def remove_user
5 5
     if request.method == "GET"
6 6
       on_get("useraccess/userremove")
15  app/models/general_request.rb
... ...
@@ -1,17 +1,8 @@
1 1
 require 'tableless_model'
2  
-require 'requester'
  2
+require 'with_requester'
3 3
 
4 4
 class GeneralRequest < TablelessModel
5  
-  attr_accessor :requester, :url, :additional, :user_agent
  5
+  include WithRequester
6 6
 
7  
-  validates_presence_of :requester
8  
-  validate do |request|
9  
-    if request.requester and not request.requester.valid?
10  
-      errors[:base] << "Requester details are either not complete or invalid."
11  
-    end
12  
-  end
13  
-
14  
-  def requester_attributes=(attr)
15  
-    self.requester = Requester.new(attr)
16  
-  end
  7
+  attr_accessor :url, :additional, :user_agent
17 8
 end
11  app/models/new_feature_request.rb
... ...
@@ -0,0 +1,11 @@
  1
+require 'tableless_model'
  2
+require 'with_requester'
  3
+require 'with_time_constraint'
  4
+
  5
+class NewFeatureRequest < TablelessModel
  6
+  include WithRequester
  7
+  include WithTimeConstraint
  8
+
  9
+  attr_accessor :user_need, :url_of_example, :inside_government
  10
+  validates_presence_of :user_need
  11
+end
9  app/models/time_constraint.rb
... ...
@@ -0,0 +1,9 @@
  1
+require 'tableless_model'
  2
+
  3
+class TimeConstraint < TablelessModel
  4
+  attr_accessor :not_before_date, :needed_by_date, :time_constraint_reason
  5
+
  6
+  validates_date :needed_by_date, :allow_nil => true, :allow_blank => true, :on_or_after => :today
  7
+  validates_date :not_before_date, :allow_nil => true, :allow_blank => true, :on_or_after => :today
  8
+  validates_date :not_before_date, :before => :needed_by_date, :unless => Proc.new { |c| c.needed_by_date.nil? || c.needed_by_date.blank? }
  9
+end
17  app/models/with_requester.rb
... ...
@@ -0,0 +1,17 @@
  1
+require 'requester'
  2
+
  3
+module WithRequester
  4
+  def self.included(base)
  5
+    base.validates_presence_of :requester
  6
+    base.validate do |request|
  7
+      if request.requester and not request.requester.valid?
  8
+        errors[:base] << "Requester details are either not complete or invalid."
  9
+      end
  10
+    end
  11
+  end
  12
+  attr_accessor :requester
  13
+
  14
+  def requester_attributes=(attr)
  15
+    self.requester = Requester.new(attr)
  16
+  end
  17
+end
16  app/models/with_time_constraint.rb
... ...
@@ -0,0 +1,16 @@
  1
+require 'time_constraint'
  2
+
  3
+module WithTimeConstraint
  4
+  def self.included(base)
  5
+    base.validate do |request|
  6
+      if request.time_constraint and not request.time_constraint.valid?
  7
+        errors[:base] << "Time constraint details are invalid."
  8
+      end
  9
+    end
  10
+  end
  11
+  attr_accessor :time_constraint
  12
+
  13
+  def time_constraint_attributes=(attr)
  14
+    self.time_constraint = TimeConstraint.new(attr)
  15
+  end
  16
+end
1  app/views/layouts/application.html.erb
@@ -34,6 +34,7 @@
34 34
               <ul class="nav nav-list">
35 35
                 <li class="nav-header">Content request</li>
36 36
                 <%= nav_link 'Content change', new_content_change_request_path %>
  37
+                <%= nav_link 'New feature/need', new_new_feature_request_path %>
37 38
               </ul>
38 39
               <ul class="nav nav-list">
39 40
                 <li class="nav-header">User Access</li>
23  app/views/new_feature_requests/new.html.erb
... ...
@@ -0,0 +1,23 @@
  1
+<%= content_for :page_title, "New feature/need" %>
  2
+<%= content_for :header, "Request a new feature/need" %>
  3
+
  4
+<div class="well">
  5
+  <%= semantic_form_for @request, :url => { :action => "create" }, :html => { :novalidate => false } do |f| %>
  6
+
  7
+    <%= render :partial => "support/requester", :locals => {:f => f} %>
  8
+
  9
+    <%= f.inputs :name => "Details of the new feature/need" do %>
  10
+
  11
+      <%= f.input :user_need, :as => :text, :required => :true, :label => "What is the user need/feature request?", :input_html => {:"aria-required" => true, :class => "span6", :rows => 6, :cols => 50 } %>
  12
+
  13
+      <%= f.input :url_of_example, :label => "Can you provide a link to an example of this feature?", :input_html => {:class => "span6", :placeholder => "http://www.example.com/"} %>
  14
+
  15
+      <%= f.input :inside_government, :as => :select, :label => "Does this affect Inside Government?", :collection => [["No", "no"], ["Yes", "yes"]], :include_blank => false %>
  16
+
  17
+    <% end %>
  18
+
  19
+    <%= render :partial => "support/time_constraint", :locals => {:f => f} %>    
  20
+
  21
+    <%= f.action :submit, :label => "Submit", :button_html => {:class => "btn btn-success"} %>
  22
+  <% end %>
  23
+</div>
16  app/views/support/_requester.html.erb
... ...
@@ -1,8 +1,10 @@
1  
-<%= f.semantic_fields_for :requester, :label => "Your details" do |r| %>
2  
-  <%= r.input :name, :label => "Name", :required => true, :input_html => {:"aria-required" => true, :class => "span6"} %>
3  
-  <%= r.input :email, :label => "Email", :as => :email, :required => true, :input_html => {:"aria-required" => true, :class => "span6"} %>
4  
-  <%= r.input :job, :label => "Job title", :required => true, :input_html => {:"aria-required" => true, :class => "span6"} %>
5  
-  <%= r.input :phone, :label => "Phone number", :as => :phone, :input_html => {:"aria-required" => true, :class => "span6"} %>
6  
-  <%= r.input :organisation, :label => "Organisation", :as => :select, :collection => @organisations, :include_blank => "Select Organisation", :input_html => {:"aria-required" => true, :class => "span6"} %>
7  
-  <%= r.input :other_organisation, :label => "Please specify the organisation if you didn't find it in the list above", :input_html => {:class => "span6"} %>
  1
+<%= f.semantic_fields_for :requester do |r| %>
  2
+  <%= r.inputs :name => "Your details" do %>
  3
+    <%= r.input :name, :label => "Name", :required => true, :input_html => {:"aria-required" => true, :class => "span6"} %>
  4
+    <%= r.input :email, :label => "Email", :as => :email, :required => true, :input_html => {:"aria-required" => true, :class => "span6"} %>
  5
+    <%= r.input :job, :label => "Job title", :required => true, :input_html => {:"aria-required" => true, :class => "span6"} %>
  6
+    <%= r.input :phone, :label => "Phone number", :as => :phone, :input_html => {:"aria-required" => true, :class => "span6"} %>
  7
+    <%= r.input :organisation, :label => "Organisation", :as => :select, :collection => @organisations, :include_blank => "Select Organisation", :input_html => {:"aria-required" => true, :class => "span6"} %>
  8
+    <%= r.input :other_organisation, :label => "Please specify the organisation if you didn't find it in the list above", :input_html => {:class => "span6"} %>
  9
+  <% end %>
8 10
 <% end %>
7  app/views/support/_time_constraint.html.erb
... ...
@@ -0,0 +1,7 @@
  1
+<%= f.semantic_fields_for :time_constraint do |r| %>
  2
+  <%= r.inputs :name => "Are there any time constraints? (Optional)" do %>
  3
+    <%= r.input :needed_by_date, :label => "MUST be published by", :required => false, :input_html => {:"calendar-enabled" => true, :placeholder => "dd-mm-YYYY", :value => r.object.needed_by_date} %>
  4
+    <%= r.input :not_before_date, :label => "MUST NOT be published BEFORE", :required => false, :input_html => {:"calendar-enabled" => true, :placeholder => "dd-mm-YYYY", :value => r.object.not_before_date} %>
  5
+    <%= r.input :time_constraint_reason, :as => :text, :label => "Reason for the above dates", :required => false, :input_html => {:class => "span6", :rows => 6, :cols => 50 } %>
  6
+  <% end %>
  7
+<% end %>
40  config/initializers/validates_timeliness.rb
... ...
@@ -0,0 +1,40 @@
  1
+ValidatesTimeliness.setup do |config|
  2
+  # Extend ORM/ODMs for full support (:active_record, :mongoid).
  3
+  # config.extend_orms = [ :active_record ]
  4
+  #
  5
+  # Default timezone
  6
+  # config.default_timezone = :utc
  7
+  #
  8
+  # Set the dummy date part for a time type values.
  9
+  # config.dummy_date_for_time_type = [ 2000, 1, 1 ]
  10
+  #
  11
+  # Ignore errors when restriction options are evaluated
  12
+  # config.ignore_restriction_errors = false
  13
+  #
  14
+  # Re-display invalid values in date/time selects
  15
+  # config.enable_date_time_select_extension!
  16
+  #
  17
+  # Handle multiparameter date/time values strictly
  18
+  # config.enable_multiparameter_extension!
  19
+  #
  20
+  # Shorthand date and time symbols for restrictions
  21
+  # config.restriction_shorthand_symbols.update(
  22
+  #   :now   => lambda { Time.current },
  23
+  #   :today => lambda { Date.current }
  24
+  # )
  25
+  #
  26
+  # Use the plugin date/time parser which is stricter and extendable
  27
+  # config.use_plugin_parser = false
  28
+  #
  29
+  # Add one or more formats making them valid. e.g. add_formats(:date, 'd(st|rd|th) of mmm, yyyy')
  30
+  # config.parser.add_formats()
  31
+  #
  32
+  # Remove one or more formats making them invalid. e.g. remove_formats(:date, 'dd/mm/yyy')
  33
+  # config.parser.remove_formats()
  34
+  #
  35
+  # Change the amiguous year threshold when parsing a 2 digit year
  36
+  # config.parser.ambiguous_year_threshold =  30
  37
+  #
  38
+  # Treat ambiguous dates, such as 01/02/1950, as a Non-US date.
  39
+  # config.parser.remove_us_formats
  40
+end
16  config/locales/validates_timeliness.en.yml
... ...
@@ -0,0 +1,16 @@
  1
+en:
  2
+  errors:
  3
+    messages:
  4
+      invalid_date: "is not a valid date"
  5
+      invalid_time: "is not a valid time"
  6
+      invalid_datetime: "is not a valid datetime"
  7
+      is_at: "must be at %{restriction}"
  8
+      before: "must be before %{restriction}"
  9
+      on_or_before: "must be on or before %{restriction}"
  10
+      after: "must be after %{restriction}"
  11
+      on_or_after: "must be on or after %{restriction}"
  12
+  validates_timeliness:
  13
+    error_value_formats:
  14
+      date: '%d-%m-%Y'
  15
+      time: '%H:%M:%S'
  16
+      datetime: '%d-%m-%Y %H:%M:%S'
1  config/routes.rb
@@ -3,6 +3,7 @@
3 3
   resource :create_new_user_request, :only => [:new, :create]
4 4
   resource :remove_user_request, :only => [:new, :create]
5 5
   resource :general_request, :only => [:new, :create]
  6
+  resource :new_feature_request, :only => [:new, :create]
6 7
 
7 8
   match "campaign" => "support#campaign"
8 9
   match "publish-tool" => "support#publish_tool"
8  features/general_requests.feature
@@ -5,16 +5,16 @@ Feature: General requests
5 5
 
6 6
   Background:
7 7
     * the following user has SSO access:
8  
-      | Name         | Email                | Job title | Organisation   |
9  
-      | John Smith   | john.smith@email.com | Developer | Cabinet Office |
  8
+      | Name         | Email                | Job title | Organisation   | Phone |
  9
+      | John Smith   | john.smith@email.com | Developer | Cabinet Office | 12345 |
10 10
 
11 11
   Scenario: successful request
12 12
     When the user submits the following general request:
13 13
       | Details          | URL               |
14 14
       | The site is down | http://www.gov.uk |
15 15
     Then the following ticket is raised in ZenDesk:
16  
-      | Subject                   | Requester email      | Requester name | Job title | Organisation   |
17  
-      | Govt Agency General Issue | john.smith@email.com | John Smith     | Developer | cabinet_office |
  16
+      | Subject                   | Requester email      | Requester name | Phone | Job title | Organisation   |
  17
+      | Govt Agency General Issue | john.smith@email.com | John Smith     | 12345 | Developer | cabinet_office |
18 18
     And the ticket is tagged with "govt_agency_general"
19 19
     And the comment on the ticket is:
20 20
       """
33  features/new_feature_requests.feature
... ...
@@ -0,0 +1,33 @@
  1
+Feature: New feature requests
  2
+  In order to fulfill user needs not currently met by gov.uk
  3
+  As a government employee
  4
+  I want a means to contact GDS and request new features
  5
+
  6
+  Background:
  7
+    * the following user has SSO access:
  8
+      | Name         | Email                | Job title | Organisation   | Phone |
  9
+      | John Smith   | john.smith@email.com | Developer | Cabinet Office | 12345 |
  10
+
  11
+  Scenario: successful request
  12
+    When the user submits the following new feature request:
  13
+      | User need          | URL of example         | Needed by date | Not before date | Reason            |
  14
+      | Information on XYZ | http://www.example.com | 31-12-2012     | 01-12-2012      | Legal requirement |
  15
+
  16
+    Then the following ticket is raised in ZenDesk:
  17
+      | Subject             | Requester email      | Requester name | Phone | Job title | Organisation   |
  18
+      | New Feature Request | john.smith@email.com | John Smith     | 12345 | Developer | cabinet_office |
  19
+    And the time constraints on the ticket are:
  20
+      | Need by date | Not before date |
  21
+      | 31-12-2012   | 01-12-2012      |
  22
+    And the ticket is tagged with "new_feature_request"
  23
+    And the comment on the ticket is:
  24
+      """
  25
+      [User need]
  26
+      Information on XYZ
  27
+
  28
+      [Url of example]
  29
+      http://www.example.com
  30
+
  31
+      [Time constraint reason]
  32
+      Legal requirement
  33
+      """
19  features/step_definitions/general_request_steps.rb
... ...
@@ -1,19 +0,0 @@
1  
-When /^the user submits the following general request:$/ do |request_details_table|
2  
-  request_details = request_details_table.hashes.first
3  
-
4  
-  visit '/'
5  
-
6  
-  click_on "General"
7  
-
8  
-  assert page.has_content?("Report a problem")
9  
-
10  
-  fill_in "Name", :with => @user_details["Name"]
11  
-  fill_in "Email", :with => @user_details["Email"]
12  
-  fill_in "Job title", :with => @user_details["Job title"]
13  
-  select @user_details["Organisation"], :from => 'Organisation'
14  
-
15  
-  fill_in "Details", :with => request_details['Details']
16  
-  fill_in "URL (if applicable)", :with => request_details['URL']
17  
-
18  
-  click_on "Submit"
19  
-end
53  features/step_definitions/request_steps.rb
... ...
@@ -0,0 +1,53 @@
  1
+When /^the user fills out their details$/ do
  2
+  fill_in "Name", :with => @user_details["Name"]
  3
+  fill_in "Email", :with => @user_details["Email"]
  4
+  fill_in "Job title", :with => @user_details["Job title"]
  5
+  fill_in "Phone number", :with => @user_details["Phone"]
  6
+  select @user_details["Organisation"], :from => 'Organisation'  
  7
+end
  8
+
  9
+When /^the user fills out the time constraints$/ do
  10
+  fill_in "MUST be published by", :with => @request_details["Needed by date"]
  11
+  fill_in "MUST NOT be published BEFORE", :with => @request_details["Not before date"]
  12
+  fill_in "Reason for the above dates", :with => @request_details["Reason"]  
  13
+end
  14
+
  15
+When /^the user submits the request successfully$/ do
  16
+  click_on "Submit"
  17
+  assert page.has_content?("Your ticket has been submitted successfully")
  18
+end
  19
+
  20
+When /^the user submits the following general request:$/ do |request_details_table|
  21
+  @request_details = request_details_table.hashes.first
  22
+
  23
+  visit '/'
  24
+
  25
+  click_on "General"
  26
+
  27
+  assert page.has_content?("Report a problem")
  28
+
  29
+  step "the user fills out their details"
  30
+
  31
+  fill_in "Details", :with => @request_details['Details']
  32
+  fill_in "URL (if applicable)", :with => @request_details['URL']
  33
+
  34
+  step "the user submits the request successfully"
  35
+end
  36
+
  37
+When /^the user submits the following new feature request:$/ do |request_details_table|
  38
+  @request_details = request_details_table.hashes.first
  39
+
  40
+  visit '/'
  41
+
  42
+  click_on "New feature/need"
  43
+
  44
+  assert page.has_content?("Request a new feature/need")
  45
+
  46
+  step "the user fills out their details"
  47
+
  48
+  fill_in "What is the user need/feature request?", :with => @request_details['User need']
  49
+  fill_in "Can you provide a link to an example of this feature?", :with => @request_details['URL of example']
  50
+
  51
+  step "the user fills out the time constraints"
  52
+  step "the user submits the request successfully"
  53
+end
9  features/step_definitions/zendesk_steps.rb
@@ -7,6 +7,7 @@
7 7
   assert_equal expected_ticket_props["Requester name"],  @raised_ticket.name
8 8
   assert_equal expected_ticket_props["Job title"],       @raised_ticket.job
9 9
   assert_equal expected_ticket_props["Organisation"],    @raised_ticket.organisation
  10
+  assert_equal expected_ticket_props["Phone"],           @raised_ticket.phone
10 11
 end
11 12
 
12 13
 Then /^the ticket is tagged with "(.*?)"$/ do |expected_tags|
@@ -15,4 +16,12 @@
15 16
 
16 17
 Then /^the comment on the ticket is:$/ do |expected_comment_string|
17 18
   assert_equal expected_comment_string, @raised_ticket.comment
  19
+end
  20
+
  21
+Then /^the time constraints on the ticket are:$/ do |ticket_properties_table|
  22
+  expected_ticket_props = ticket_properties_table.hashes.first
  23
+  @raised_ticket = @zendesk_api.ticket
  24
+
  25
+  assert_equal expected_ticket_props["Need by date"], @raised_ticket.needed_by_date
  26
+  assert_equal expected_ticket_props["Not before date"], @raised_ticket.not_before_date
18 27
 end
17  lib/general_request_zendesk_ticket.rb
@@ -3,8 +3,6 @@
3 3
 require 'comment_snippet'
4 4
 
5 5
 class GeneralRequestZendeskTicket < ZendeskTicket
6  
-  def_delegators :@requester, :name, :email, :organisation, :job
7  
-
8 6
   def initialize(request)
9 7
     super(request, nil)
10 8
     @requester = request.requester
@@ -18,19 +16,8 @@ def request_specific_tag
18 16
     "govt_agency_general"
19 17
   end
20 18
 
21  
-  # the following method will be pushed down to the superclass as soon as everything is converted to ActiveModel
22  
-  def comment
23  
-    applicable_snippets = comment_snippets.select(&:applies?)
24  
-    applicable_snippets.collect(&:to_s).join("\n\n")
25  
-  end
26  
-
27  
-  def phone
28  
-    if has_value(:phone, @request)
29  
-      remove_space_from_phone_number(@requester.phone)
30  
-    else
31  
-      nil
32  
-    end
33  
-  end
  19
+  # the following methods will be pushed down to the superclass as soon as everything is converted to ActiveModel
  20
+  def_delegators :@requester, :name, :email, :organisation, :job
34 21
 
35 22
   protected
36 23
   def comment_snippets
32  lib/new_feature_request_zendesk_ticket.rb
... ...
@@ -0,0 +1,32 @@
  1
+require 'zendesk_ticket'
  2
+require 'forwardable'
  3
+require 'comment_snippet'
  4
+
  5
+class NewFeatureRequestZendeskTicket < ZendeskTicket
  6
+  attr_reader :time_constraint
  7
+
  8
+  def initialize(request)
  9
+    super(request, nil)
  10
+    @requester = request.requester
  11
+    @time_constraint = request.time_constraint
  12
+  end
  13
+
  14
+  def subject
  15
+    "New Feature Request"
  16
+  end
  17
+
  18
+  def request_specific_tag
  19
+    "new_feature_request"
  20
+  end
  21
+
  22
+  # the following methods will be pushed down to the superclass as soon as everything is converted to ActiveModel
  23
+  def_delegators :@requester, :name, :email, :organisation, :job
  24
+
  25
+  protected
  26
+  def comment_snippets
  27
+    [ CommentSnippet.new(@request.requester, :other_organisation),
  28
+      CommentSnippet.new(@request, :user_need),
  29
+      CommentSnippet.new(@request, :url_of_example),
  30
+      CommentSnippet.new(@request.time_constraint, :time_constraint_reason) ]
  31
+  end
  32
+end
4  lib/zendesk_request.rb
@@ -6,7 +6,7 @@ def self.field_ids
6 6
     { organisation:    "21494928",
7 7
       job:             "21487987",
8 8
       phone:           "21471291",
9  
-      need_by_date:    "21485833",
  9
+      needed_by_date:  "21485833",
10 10
       not_before_date: "21502036" }
11 11
   end
12 12
 
@@ -29,7 +29,7 @@ def self.raise_ticket(client, ticket_to_raise)
29 29
       :fields => [{"id" => ZendeskRequest.field_ids[:organisation],    "value" => ticket_to_raise.organisation},
30 30
                   {"id" => ZendeskRequest.field_ids[:job],             "value" => ticket_to_raise.job},
31 31
                   {"id" => ZendeskRequest.field_ids[:phone],           "value" => ticket_to_raise.phone},
32  
-                  {"id" => ZendeskRequest.field_ids[:need_by_date],    "value" => ticket_to_raise.need_by_date},
  32
+                  {"id" => ZendeskRequest.field_ids[:needed_by_date],  "value" => ticket_to_raise.needed_by_date},
33 33
                   {"id" => ZendeskRequest.field_ids[:not_before_date], "value" => ticket_to_raise.not_before_date}],
34 34
       :tags => ticket_to_raise.tags,
35 35
       :comment => {:value => ticket_to_raise.comment})
42  lib/zendesk_ticket.rb
... ...
@@ -1,4 +1,5 @@
1 1
 require 'forwardable'
  2
+require 'date'
2 3
 
3 4
 class ZendeskTicket
4 5
   extend Forwardable
@@ -32,15 +33,30 @@ def initialize(request, from_route)
32 33
   def_delegators :@request, :name, :email, :organisation, :job
33 34
 
34 35
   def phone
35  
-    if has_value(:phone)
36  
-      remove_space_from_phone_number(@request.phone)
  36
+    # TODO: solve this horrible mess when the refactor is done
  37
+    if instance_variable_defined?("@requester")
  38
+      if has_value?(:phone, @requester)
  39
+        remove_space_from_phone_number(@requester.phone)
  40
+      else
  41
+        nil
  42
+      end
37 43
     else
38  
-      nil
  44
+      if has_value?(:phone)
  45
+        remove_space_from_phone_number(@request.phone)
  46
+      else
  47
+        nil
  48
+      end
39 49
     end
40 50
   end
41 51
 
42 52
   def comment
43  
-    format_comment(@from_route, @request)
  53
+    if respond_to?(:comment_snippets)
  54
+      applicable_snippets = comment_snippets.select(&:applies?)
  55
+      applicable_snippets.collect(&:to_s).join("\n\n")
  56
+    else
  57
+      # TODO: remove this when the refactor is complete
  58
+      format_comment(@from_route, @request)
  59
+    end
44 60
   end
45 61
 
46 62
   def subject
@@ -48,15 +64,21 @@ def subject
48 64
   end
49 65
 
50 66
   def not_before_date
51  
-    if has_value(:not_before_day)
  67
+    # TODO sort this mess out
  68
+    if has_value?(:time_constraint) and has_value?(:not_before_date, @request.time_constraint)
  69
+      @request.time_constraint.not_before_date
  70
+    elsif has_value?(:not_before_day)
52 71
       @request.not_before_day + "/" + @request.not_before_month + "/" + @request.not_before_year
53 72
     else
54 73
       nil
55 74
     end
56 75
   end
57 76
 
58  
-  def need_by_date
59  
-    if has_value(:need_by_day)
  77
+  def needed_by_date
  78
+    # TODO sort this mess out
  79
+    if has_value?(:time_constraint) and has_value?(:needed_by_date, @request.time_constraint)
  80
+      @request.time_constraint.needed_by_date
  81
+    elsif has_value?(:need_by_day)
60 82
       @request.need_by_day + "/" + @request.need_by_month + "/" + @request.need_by_year
61 83
     else
62 84
       nil
@@ -74,16 +96,16 @@ def tags
74 96
   private
75 97
 
76 98
   def inside_government_tag
77  
-    if has_value(:inside_government) and @request.inside_government == "yes"
  99
+    if has_value?(:inside_government) and @request.inside_government == "yes"
78 100
       ["inside_government"]
79 101
     else
80 102
       []
81 103
     end
82 104
   end
83 105
     
84  
-  def has_value(param, target = nil)
  106
+  def has_value?(param, target = nil)
85 107
     target ||= @request
86  
-    target.respond_to?(param) and not target.send(param).nil? and not target.send(param).strip.empty?
  108
+    target.respond_to?(param) and not target.send(param).nil? and not target.send(param).to_s.strip.empty?
87 109
   end
88 110
 
89 111
   def format_comment(from_route, request)
7  test/unit/models/new_feature_request_test.rb
... ...
@@ -0,0 +1,7 @@
  1
+require 'test_helper'
  2
+
  3
+class NewFeatureRequestTest < Test::Unit::TestCase
  4
+  should validate_presence_of(:requester)
  5
+  should validate_presence_of(:user_need)
  6
+  should allow_value("yes").for(:inside_government)
  7
+end
35  test/unit/models/time_constraint_test.rb
... ...
@@ -0,0 +1,35 @@
  1
+require 'test_helper'
  2
+
  3
+class TimeConstraintTest < Test::Unit::TestCase
  4
+  def self.as_str(date)
  5
+    date.strftime("%d-%m-%Y")
  6
+  end
  7
+
  8
+  def as_str(date)
  9
+    TimeConstraintTest.as_str(date)
  10
+  end
  11
+
  12
+  should_not allow_value("xxx").for(:needed_by_date)
  13
+
  14
+  should allow_value(as_str(Date.tomorrow)).for(:needed_by_date)
  15
+  should allow_value(as_str(Date.today)).for(:needed_by_date)
  16
+  should_not allow_value(as_str(Date.yesterday)).for(:needed_by_date)
  17
+
  18
+  should_not allow_value("xxx").for(:not_before_date)
  19
+  
  20
+  should allow_value(as_str(Date.tomorrow)).for(:not_before_date)
  21
+  should allow_value(as_str(Date.today)).for(:not_before_date)
  22
+  should_not allow_value(as_str(Date.yesterday)).for(:not_before_date)
  23
+
  24
+  should "allow the 'not before' and 'needed by' dates to be blank" do
  25
+    constraint = TimeConstraint.new(:not_before_date => "", :needed_by_date  => "")
  26
+    assert constraint.valid?
  27
+  end
  28
+
  29
+  should "not allow the 'not before' date to be set after the 'needed by' date" do
  30
+    constraint = TimeConstraint.new(:not_before_date => as_str(Date.tomorrow + 1.day), 
  31
+                                    :needed_by_date  => as_str(Date.tomorrow))
  32
+    assert !constraint.valid?
  33
+    assert constraint.errors[:not_before_date].size > 0
  34
+  end
  35
+end
27  test/unit/zendesk_ticket_test.rb
@@ -3,6 +3,7 @@
3 3
 require 'zendesk_ticket'
4 4
 require 'test_data'
5 5
 require 'ostruct'
  6
+require 'date'
6 7
 
7 8
 class ZendeskTicketTest < Test::Unit::TestCase
8 9
   def new_ticket(attributes, type = nil)
@@ -34,14 +35,28 @@ def new_ticket(attributes, type = nil)
34 35
       assert_equal "123456", ticket.phone
35 36
     end
36 37
 
37  
-    should "concatenate the need_by_date correctly" do
38  
-      options = {need_by_day: "01", need_by_month: "02", need_by_year: "2012"}
39  
-      assert_equal "01/02/2012", new_ticket(options).need_by_date
  38
+    context "old design" do
  39
+      should "concatenate the needed_by_date correctly" do
  40
+        options = {need_by_day: "01", need_by_month: "02", need_by_year: "2012"}
  41
+        assert_equal "01/02/2012", new_ticket(options).needed_by_date
  42
+      end
  43
+
  44
+      should "concatenate the not_before_date correctly" do
  45
+        options = {not_before_day: "01", not_before_month: "02", not_before_year: "2012"}
  46
+        assert_equal "01/02/2012", new_ticket(options).not_before_date
  47
+      end
40 48
     end
41 49
 
42  
-    should "concatenate the not_before_date correctly" do
43  
-      options = {not_before_day: "01", not_before_month: "02", not_before_year: "2012"}
44  
-      assert_equal "01/02/2012", new_ticket(options).not_before_date
  50
+    context "with time constraints" do
  51
+      should "pass the need_by_date through" do
  52
+        time_constraint = OpenStruct.new(needed_by_date: "03-02-2001")
  53
+        assert_equal "03-02-2001", new_ticket(time_constraint: time_constraint).needed_by_date
  54
+      end
  55
+
  56
+      should "pass the not_before_date through" do
  57
+        time_constraint = OpenStruct.new(not_before_date: "03-02-2001")
  58
+        assert_equal "03-02-2001", new_ticket(time_constraint: time_constraint).not_before_date
  59
+      end
45 60
     end
46 61
 
47 62
     should "set the subject according to request type" do
0  vendor/assets/javascripts/.gitkeep
No changes.
0  vendor/assets/stylesheets/.gitkeep
No changes.
0  vendor/plugins/.gitkeep
No changes.

0 notes on commit c196ced

Please sign in to comment.
Something went wrong with that request. Please try again.