Commit dc526592cfaeb3a8bccbc4715c26c8f114f1c2a7

Authored by Nathan Broadbent
1 parent e7ff4c6f
Exists in master and in 1 other branch production

Added a button to copy settings from another app when editing or creating an app…

…. (We needed to set up 10+ apps using the same issue tracker and watcher email). Works great, and is tested, but I wouldn't mind some help on the UI.
app/controllers/apps_controller.rb
@@ -34,12 +34,12 @@ class AppsController < InheritedResources::Base @@ -34,12 +34,12 @@ class AppsController < InheritedResources::Base
34 end 34 end
35 35
36 def new 36 def new
37 - plug_params build_resource 37 + plug_params(build_resource)
38 new! 38 new!
39 end 39 end
40 40
41 def edit 41 def edit
42 - plug_params resource 42 + plug_params(resource)
43 edit! 43 edit!
44 end 44 end
45 45
@@ -63,6 +63,7 @@ class AppsController < InheritedResources::Base @@ -63,6 +63,7 @@ class AppsController < InheritedResources::Base
63 def plug_params app 63 def plug_params app
64 app.watchers.build if app.watchers.none? 64 app.watchers.build if app.watchers.none?
65 app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured? 65 app.issue_tracker = IssueTracker.new unless app.issue_tracker_configured?
  66 + app.copy_attributes_from(params[:copy_attributes_from]) if params[:copy_attributes_from]
66 end 67 end
67 68
68 # email_at_notices is edited as a string, and stored as an array. 69 # email_at_notices is edited as a string, and stored as an array.
app/helpers/apps_helper.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +module AppsHelper
  2 + def link_to_copy_attributes_from_other_app
  3 + if App.count > 1
  4 + html = link_to('copy settings from another app', '#',
  5 + :class => 'button copy_config')
  6 + html << select("duplicate", "app",
  7 + App.all.reject{|a| a == @app }.
  8 + collect{|p| [ p.name, p.id ] }, {:include_blank => "[choose app]"},
  9 + {:class => "choose_other_app", :style => "display: none;"})
  10 + return html
  11 + end
  12 + end
  13 +end
  14 +
app/models/app.rb
@@ -78,6 +78,22 @@ class App @@ -78,6 +78,22 @@ class App
78 notify_all_users ? User.all.map(&:email).reject(&:blank?) : watchers.map(&:address) 78 notify_all_users ? User.all.map(&:email).reject(&:blank?) : watchers.map(&:address)
79 end 79 end
80 80
  81 + # Copy app attributes from another app.
  82 + def copy_attributes_from(app_id)
  83 + if copy_app = App.where(:_id => app_id).first
  84 + # Copy fields
  85 + (copy_app.fields.keys - %w(_id name created_at updated_at)).each do |k|
  86 + self.send("#{k}=", copy_app.send(k))
  87 + end
  88 + # Clone the embedded objects that can be changed via apps/edit (ignore errs & deploys, etc.)
  89 + %w(watchers issue_tracker).each do |relation|
  90 + if obj = copy_app.send(relation)
  91 + self.send("#{relation}=", obj.is_a?(Array) ? obj.map(&:clone) : obj.clone)
  92 + end
  93 + end
  94 + end
  95 + end
  96 +
81 protected 97 protected
82 98
83 def generate_api_key 99 def generate_api_key
app/views/apps/edit.html.haml
1 - content_for :title, 'Edit App' 1 - content_for :title, 'Edit App'
2 -- content_for :action_bar, link_to('cancel', app_path(@app), :class => 'button') 2 +- content_for :action_bar do
  3 + = link_to_copy_attributes_from_other_app
  4 + = link_to('cancel', app_path(@app), :class => 'button')
3 5
4 = form_for @app do |f| 6 = form_for @app do |f|
5 - 7 +
6 = render 'fields', :f => f 8 = render 'fields', :f => f
7 -  
8 - %div.buttons= f.submit 'Update App'  
9 \ No newline at end of file 9 \ No newline at end of file
  10 +
  11 + %div.buttons= f.submit 'Update App'
  12 +
app/views/apps/new.html.haml
1 - content_for :title, 'Add App' 1 - content_for :title, 'Add App'
2 -- content_for :action_bar, link_to('cancel', apps_path, :class => 'button') 2 +- content_for :action_bar do
  3 + = link_to_copy_attributes_from_other_app
  4 + = link_to('cancel', apps_path, :class => 'button')
3 5
4 = form_for @app do |f| 6 = form_for @app do |f|
5 - 7 +
6 = render 'fields', :f => f 8 = render 'fields', :f => f
7 -  
8 - %div.buttons= f.submit 'Add App'  
9 \ No newline at end of file 9 \ No newline at end of file
  10 +
  11 + %div.buttons= f.submit 'Add App'
  12 +
public/javascripts/application.js
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 2
3 $(function(){ 3 $(function(){
4 activateTabbedPanels(); 4 activateTabbedPanels();
5 - 5 +
6 $('#watcher_name').live("click", function() { 6 $('#watcher_name').live("click", function() {
7 $(this).closest('form').find('.show').removeClass('show'); 7 $(this).closest('form').find('.show').removeClass('show');
8 $('#app_watchers_attributes_0_user_id').addClass('show'); 8 $('#app_watchers_attributes_0_user_id').addClass('show');
@@ -13,6 +13,14 @@ $(function(){ @@ -13,6 +13,14 @@ $(function(){
13 $('#app_watchers_attributes_0_email').addClass('show'); 13 $('#app_watchers_attributes_0_email').addClass('show');
14 }); 14 });
15 15
  16 + $('a.copy_config').live("click", function() {
  17 + $('select.choose_other_app').show().focus();
  18 + });
  19 + $('select.choose_other_app').live("change", function() {
  20 + var loc = window.location;
  21 + window.location.href = loc.protocol + "//" + loc.host + loc.pathname +
  22 + "?copy_attributes_from=" + $(this).val();
  23 + });
16 }); 24 });
17 25
18 function activateTabbedPanels() { 26 function activateTabbedPanels() {
@@ -22,8 +30,8 @@ function activateTabbedPanels() { @@ -22,8 +30,8 @@ function activateTabbedPanels() {
22 panel.addClass('panel'); 30 panel.addClass('panel');
23 panel.find('h3').hide(); 31 panel.find('h3').hide();
24 }) 32 })
25 -  
26 - $('.tab-bar a').click(function(){ 33 +
  34 + $('.tab-bar a').click(function(){
27 activateTab($(this)); 35 activateTab($(this));
28 return(false); 36 return(false);
29 }); 37 });
@@ -33,10 +41,11 @@ function activateTabbedPanels() { @@ -33,10 +41,11 @@ function activateTabbedPanels() {
33 function activateTab(tab) { 41 function activateTab(tab) {
34 tab = $(tab); 42 tab = $(tab);
35 var panel = $('#'+tab.attr('rel')); 43 var panel = $('#'+tab.attr('rel'));
36 - 44 +
37 tab.closest('.tab-bar').find('a.active').removeClass('active'); 45 tab.closest('.tab-bar').find('a.active').removeClass('active');
38 tab.addClass('active'); 46 tab.addClass('active');
39 - 47 +
40 $('.panel').hide(); 48 $('.panel').hide();
41 panel.show(); 49 panel.show();
42 -}  
43 \ No newline at end of file 50 \ No newline at end of file
  51 +}
  52 +
spec/controllers/apps_controller_spec.rb
@@ -173,6 +173,16 @@ describe AppsController do @@ -173,6 +173,16 @@ describe AppsController do
173 assigns(:app).should be_new_record 173 assigns(:app).should be_new_record
174 assigns(:app).watchers.should_not be_empty 174 assigns(:app).watchers.should_not be_empty
175 end 175 end
  176 +
  177 + it "should copy attributes from an existing app" do
  178 + @app = Factory(:app, :name => "do not copy",
  179 + :github_url => "github.com/test/example")
  180 + get :new, :copy_attributes_from => @app.id
  181 + assigns(:app).should be_a(App)
  182 + assigns(:app).should be_new_record
  183 + assigns(:app).name.should be_blank
  184 + assigns(:app).github_url.should == "github.com/test/example"
  185 + end
176 end 186 end
177 187
178 describe "GET /apps/:id/edit" do 188 describe "GET /apps/:id/edit" do
spec/models/app_spec.rb
@@ -99,5 +99,16 @@ describe App do @@ -99,5 +99,16 @@ describe App do
99 end 99 end
100 end 100 end
101 101
  102 + context "copying attributes from existing app" do
  103 + it "should only copy the necessary fields" do
  104 + @app, @copy_app = Factory(:app, :name => "app", :github_url => "url"),
  105 + Factory(:app, :name => "copy_app", :github_url => "copy url")
  106 + @copy_watcher = Factory(:watcher, :email => "copywatcher@example.com", :app => @copy_app)
  107 + @app.copy_attributes_from(@copy_app.id)
  108 + @app.name.should == "app"
  109 + @app.github_url.should == "copy url"
  110 + @app.watchers.first.email.should == "copywatcher@example.com"
  111 + end
  112 + end
102 end 113 end
103 114