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 | 9 | @errs = @project.errs.paginate |
10 | 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 | 50 | end | ... | ... |
app/models/project.rb
... | ... | @@ -8,13 +8,16 @@ class Project |
8 | 8 | |
9 | 9 | embeds_many :watchers |
10 | 10 | embeds_many :deploys |
11 | - references_many :errs | |
11 | + references_many :errs, :dependent => :destroy | |
12 | 12 | |
13 | 13 | before_validation :generate_api_key, :on => :create |
14 | 14 | |
15 | 15 | validates_presence_of :name, :api_key |
16 | 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 | 21 | def self.find_by_api_key!(key) |
19 | 22 | where(:api_key => key).first || raise(Mongoid::Errors::DocumentNotFound.new(self,key)) |
20 | 23 | end | ... | ... |
app/views/errs/_table.html.haml
... | ... | @@ -20,4 +20,8 @@ |
20 | 20 | %em= err.where |
21 | 21 | %td.latest #{time_ago_in_words(err.last_notice_at)} ago |
22 | 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 | 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 | 29 | \ No newline at end of file | ... | ... |
app/views/layouts/application.html.haml
... | ... | @@ -5,6 +5,7 @@ |
5 | 5 | Hypnotoad — |
6 | 6 | = yield(:page_title).present? ? yield(:page_title) : yield(:title) |
7 | 7 | %meta{ :content => "text/html; charset=utf-8", "http-equiv" => "content-type" }/ |
8 | + = csrf_meta_tag | |
8 | 9 | = javascript_include_tag :defaults |
9 | 10 | = stylesheet_link_tag 'reset', 'application' |
10 | 11 | = yield :head | ... | ... |
app/views/projects/_configuration_instructions.html.haml
0 → 100644
... | ... | @@ -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 | 22 | \ No newline at end of file | ... | ... |
app/views/projects/index.html.haml
app/views/projects/show.html.haml
... | ... | @@ -7,7 +7,25 @@ |
7 | 7 | - content_for :action_bar do |
8 | 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 | 30 | %h3 Errors |
13 | 31 | = render 'errs/table', :errs => @errs |
14 | 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 | 151 | table tbody tr:nth-child(odd) td { background-color: #F9F9F9; } |
152 | 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 | 165 | /* HTML Styling */ |
155 | 166 | .html { padding-left: 1em; border-left: 2px solid #C6C6C6;} |
156 | 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 | 19 | end |
20 | 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 | 128 | end | ... | ... |