Commit ce442af995f39734dd3d9d827b634b586c476fcd
1 parent
7e53d6cc
Exists in
master
and in
1 other branch
Add full management support for projects
Showing
12 changed files
with
232 additions
and
3 deletions
Show diff stats
app/controllers/projects_controller.rb
@@ -9,4 +9,42 @@ class ProjectsController < ApplicationController | @@ -9,4 +9,42 @@ class ProjectsController < ApplicationController | ||
9 | @errs = @project.errs.paginate | 9 | @errs = @project.errs.paginate |
10 | end | 10 | end |
11 | 11 | ||
12 | + def new | ||
13 | + @project = Project.new | ||
14 | + @project.watchers.build | ||
15 | + end | ||
16 | + | ||
17 | + def edit | ||
18 | + @project = Project.find(params[:id]) | ||
19 | + @project.watchers.build if @project.watchers.none? | ||
20 | + end | ||
21 | + | ||
22 | + def create | ||
23 | + @project = Project.new(params[:project]) | ||
24 | + | ||
25 | + if @project.save | ||
26 | + flash[:success] = 'Great success! Configure your project with the API key below' | ||
27 | + redirect_to project_path(@project) | ||
28 | + else | ||
29 | + render :new | ||
30 | + end | ||
31 | + end | ||
32 | + | ||
33 | + def update | ||
34 | + @project = Project.find(params[:id]) | ||
35 | + | ||
36 | + if @project.update_attributes(params[:project]) | ||
37 | + flash[:success] = "Good news everyone! '#{@project.name}' was successfully updated." | ||
38 | + redirect_to project_path(@project) | ||
39 | + else | ||
40 | + render :edit | ||
41 | + end | ||
42 | + end | ||
43 | + | ||
44 | + def destroy | ||
45 | + @project = Project.find(params[:id]) | ||
46 | + @project.destroy | ||
47 | + flash[:success] = "'#{@project.name}' was successfully destroyed." | ||
48 | + redirect_to projects_path | ||
49 | + end | ||
12 | end | 50 | end |
app/models/project.rb
@@ -8,13 +8,16 @@ class Project | @@ -8,13 +8,16 @@ class Project | ||
8 | 8 | ||
9 | embeds_many :watchers | 9 | embeds_many :watchers |
10 | embeds_many :deploys | 10 | embeds_many :deploys |
11 | - references_many :errs | 11 | + references_many :errs, :dependent => :destroy |
12 | 12 | ||
13 | before_validation :generate_api_key, :on => :create | 13 | before_validation :generate_api_key, :on => :create |
14 | 14 | ||
15 | validates_presence_of :name, :api_key | 15 | validates_presence_of :name, :api_key |
16 | validates_uniqueness_of :name, :api_key, :allow_blank => true | 16 | validates_uniqueness_of :name, :api_key, :allow_blank => true |
17 | 17 | ||
18 | + accepts_nested_attributes_for :watchers, | ||
19 | + :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } } | ||
20 | + | ||
18 | def self.find_by_api_key!(key) | 21 | def self.find_by_api_key!(key) |
19 | where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) | 22 | where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) |
20 | end | 23 | end |
app/views/errs/_table.html.haml
@@ -20,4 +20,8 @@ | @@ -20,4 +20,8 @@ | ||
20 | %em= err.where | 20 | %em= err.where |
21 | %td.latest #{time_ago_in_words(err.last_notice_at)} ago | 21 | %td.latest #{time_ago_in_words(err.last_notice_at)} ago |
22 | %td.deploy= err.project.last_deploy_at ? err.project.last_deploy_at.to_date.to_s(:micro) : 'n/a' | 22 | %td.deploy= err.project.last_deploy_at ? err.project.last_deploy_at.to_date.to_s(:micro) : 'n/a' |
23 | - %td.count= err.notices.count | ||
24 | \ No newline at end of file | 23 | \ No newline at end of file |
24 | + %td.count= err.notices.count | ||
25 | + - if errs.none? | ||
26 | + %tr | ||
27 | + %td{:colspan => (@project ? 5 : 6)} | ||
28 | + %em No errors have been caught, yet | ||
25 | \ No newline at end of file | 29 | \ No newline at end of file |
app/views/layouts/application.html.haml
@@ -5,6 +5,7 @@ | @@ -5,6 +5,7 @@ | ||
5 | Hypnotoad — | 5 | Hypnotoad — |
6 | = yield(:page_title).present? ? yield(:page_title) : yield(:title) | 6 | = yield(:page_title).present? ? yield(:page_title) : yield(:title) |
7 | %meta{ :content => "text/html; charset=utf-8", "http-equiv" => "content-type" }/ | 7 | %meta{ :content => "text/html; charset=utf-8", "http-equiv" => "content-type" }/ |
8 | + = csrf_meta_tag | ||
8 | = javascript_include_tag :defaults | 9 | = javascript_include_tag :defaults |
9 | = stylesheet_link_tag 'reset', 'application' | 10 | = stylesheet_link_tag 'reset', 'application' |
10 | = yield :head | 11 | = yield :head |
app/views/projects/_configuration_instructions.html.haml
0 → 100644
@@ -0,0 +1,21 @@ | @@ -0,0 +1,21 @@ | ||
1 | +%pre | ||
2 | + %code | ||
3 | + :preserve | ||
4 | + | ||
5 | + # Require the hoptoad_notifier gem in you App. | ||
6 | + # | ||
7 | + # Rails 3 - In your Gemfile | ||
8 | + # gem 'hoptoad_notifier' | ||
9 | + # | ||
10 | + # Rails 2 - In environment.rb | ||
11 | + # config.gem 'hoptoad_notifier' | ||
12 | + # | ||
13 | + # Then add the following to config/initializers/hoptoad.rb | ||
14 | + HoptoadNotifier.configure do |config| | ||
15 | + config.api_key = '#{project.api_key}' | ||
16 | + config.host = '#{request.host}' | ||
17 | + config.port = #{request.port} | ||
18 | + # Note: Deployment notifications only work on port 80 | ||
19 | + end | ||
20 | + | ||
21 | + | ||
0 | \ No newline at end of file | 22 | \ No newline at end of file |
@@ -0,0 +1,8 @@ | @@ -0,0 +1,8 @@ | ||
1 | +- content_for :title, 'Edit Project' | ||
2 | +- content_for :action_bar, link_to('cancel', project_path(@project)) | ||
3 | + | ||
4 | += form_for @project do |f| | ||
5 | + | ||
6 | + = render 'fields', :f => f | ||
7 | + | ||
8 | + %div= f.submit 'Update' | ||
0 | \ No newline at end of file | 9 | \ No newline at end of file |
app/views/projects/index.html.haml
app/views/projects/show.html.haml
@@ -7,7 +7,25 @@ | @@ -7,7 +7,25 @@ | ||
7 | - content_for :action_bar do | 7 | - content_for :action_bar do |
8 | = link_to 'edit', edit_project_path(@project) | 8 | = link_to 'edit', edit_project_path(@project) |
9 | | | 9 | | |
10 | - = link_to 'destroy', project_path(@project), :method => :destroy, :confirm => 'Seriously?' | 10 | + = link_to 'destroy', project_path(@project), :method => :delete, :confirm => 'Seriously?' |
11 | + | ||
12 | +- if @project.errs.none? | ||
13 | + %h3 Setup your app | ||
14 | + = render 'configuration_instructions', :project => @project | ||
15 | + | ||
16 | +%h3 Watchers | ||
17 | +%table.watchers | ||
18 | + %thead | ||
19 | + %tr | ||
20 | + %th Email | ||
21 | + %tbody | ||
22 | + - @project.watchers.each do |watcher| | ||
23 | + %tr | ||
24 | + %td= watcher.email | ||
25 | + - if @project.watchers.none? | ||
26 | + %tr | ||
27 | + %td | ||
28 | + %em Sadly, no one is watching this project | ||
11 | 29 | ||
12 | %h3 Errors | 30 | %h3 Errors |
13 | = render 'errs/table', :errs => @errs | 31 | = render 'errs/table', :errs => @errs |
14 | \ No newline at end of file | 32 | \ No newline at end of file |
public/stylesheets/application.css
@@ -151,6 +151,17 @@ table th { background-color: #E2E2E2; font-weight: bold; text-transform: upperca | @@ -151,6 +151,17 @@ table th { background-color: #E2E2E2; font-weight: bold; text-transform: upperca | ||
151 | table tbody tr:nth-child(odd) td { background-color: #F9F9F9; } | 151 | table tbody tr:nth-child(odd) td { background-color: #F9F9F9; } |
152 | table .main { width: 100%; } | 152 | table .main { width: 100%; } |
153 | 153 | ||
154 | +/* Code */ | ||
155 | +pre { | ||
156 | + padding: 0.8em; | ||
157 | + margin-bottom: 1em; | ||
158 | + color: #f0f0f0; | ||
159 | + background-color: #222; | ||
160 | + border: 1px solid #444; | ||
161 | + font-family: monaco, courier, monospace; | ||
162 | + font-size: 1.1em; | ||
163 | +} | ||
164 | + | ||
154 | /* HTML Styling */ | 165 | /* HTML Styling */ |
155 | .html { padding-left: 1em; border-left: 2px solid #C6C6C6;} | 166 | .html { padding-left: 1em; border-left: 2px solid #C6C6C6;} |
156 | .html h1, .html h2, .html h3, .html h4, .html h5, .html h6 { | 167 | .html h1, .html h2, .html h3, .html h4, .html h5, .html h6 { |
spec/controllers/projects_controller_spec.rb
@@ -19,4 +19,110 @@ describe ProjectsController do | @@ -19,4 +19,110 @@ describe ProjectsController do | ||
19 | end | 19 | end |
20 | end | 20 | end |
21 | 21 | ||
22 | + describe "GET /projects/new" do | ||
23 | + it 'instantiates a new project with a prebuilt watcher' do | ||
24 | + get :new | ||
25 | + assigns(:project).should be_a(Project) | ||
26 | + assigns(:project).should be_new_record | ||
27 | + assigns(:project).watchers.should_not be_empty | ||
28 | + end | ||
29 | + end | ||
30 | + | ||
31 | + describe "GET /projects/:id/edit" do | ||
32 | + it 'finds the correct project' do | ||
33 | + project = Factory(:project) | ||
34 | + get :edit, :id => project.id | ||
35 | + assigns(:project).should == project | ||
36 | + end | ||
37 | + end | ||
38 | + | ||
39 | + describe "POST /projects" do | ||
40 | + before do | ||
41 | + @project = Factory(:project) | ||
42 | + Project.stub(:new).and_return(@project) | ||
43 | + end | ||
44 | + | ||
45 | + context "when the create is successful" do | ||
46 | + before do | ||
47 | + @project.should_receive(:save).and_return(true) | ||
48 | + end | ||
49 | + | ||
50 | + it "should redirect to the project page" do | ||
51 | + post :create, :project => {} | ||
52 | + response.should redirect_to(project_path(@project)) | ||
53 | + end | ||
54 | + | ||
55 | + it "should display a message" do | ||
56 | + post :create, :project => {} | ||
57 | + request.flash[:success].should match(/success/) | ||
58 | + end | ||
59 | + end | ||
60 | + | ||
61 | + context "when the create is unsuccessful" do | ||
62 | + it "should render the new page" do | ||
63 | + @project.should_receive(:save).and_return(false) | ||
64 | + post :create, :project => {} | ||
65 | + response.should render_template(:new) | ||
66 | + end | ||
67 | + end | ||
68 | + end | ||
69 | + | ||
70 | + describe "PUT /projects/:id" do | ||
71 | + before do | ||
72 | + @project = Factory(:project) | ||
73 | + Project.stub(:find).with(@project.id).and_return(@project) | ||
74 | + end | ||
75 | + | ||
76 | + context "when the update is successful" do | ||
77 | + before do | ||
78 | + @project.should_receive(:update_attributes).and_return(true) | ||
79 | + end | ||
80 | + | ||
81 | + it "should redirect to the project page" do | ||
82 | + put :update, :id => @project.id, :project => {} | ||
83 | + response.should redirect_to(project_path(@project)) | ||
84 | + end | ||
85 | + | ||
86 | + it "should display a message" do | ||
87 | + put :update, :id => @project.id, :project => {} | ||
88 | + request.flash[:success].should match(/success/) | ||
89 | + end | ||
90 | + end | ||
91 | + | ||
92 | + context "when the update is unsuccessful" do | ||
93 | + it "should render the edit page" do | ||
94 | + @project.should_receive(:update_attributes).and_return(false) | ||
95 | + put :update, :id => @project.id, :project => {} | ||
96 | + response.should render_template(:edit) | ||
97 | + end | ||
98 | + end | ||
99 | + end | ||
100 | + | ||
101 | + describe "DELETE /projects/:id" do | ||
102 | + before do | ||
103 | + @project = Factory(:project) | ||
104 | + Project.stub(:find).with(@project.id).and_return(@project) | ||
105 | + end | ||
106 | + | ||
107 | + it "should find the project" do | ||
108 | + delete :destroy, :id => @project.id | ||
109 | + assigns(:project).should == @project | ||
110 | + end | ||
111 | + | ||
112 | + it "should destroy the project" do | ||
113 | + @project.should_receive(:destroy) | ||
114 | + delete :destroy, :id => @project.id | ||
115 | + end | ||
116 | + | ||
117 | + it "should display a message" do | ||
118 | + delete :destroy, :id => @project.id | ||
119 | + request.flash[:success].should match(/success/) | ||
120 | + end | ||
121 | + | ||
122 | + it "should redirect to the projects page" do | ||
123 | + delete :destroy, :id => @project.id | ||
124 | + response.should redirect_to(projects_path) | ||
125 | + end | ||
126 | + end | ||
127 | + | ||
22 | end | 128 | end |