Commit b9d989dc056a2a2b9316ff9aa06b57c736426871
Exists in
master
and in
4 other branches
Merge branch 'Andrew8xx8-gist'
Showing
60 changed files
with
972 additions
and
210 deletions
Show diff stats
app/controllers/projects/application_controller.rb
... | ... | @@ -0,0 +1,91 @@ |
1 | +class Projects::SnippetsController < Projects::ApplicationController | |
2 | + before_filter :module_enabled | |
3 | + before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw] | |
4 | + | |
5 | + # Allow read any snippet | |
6 | + before_filter :authorize_read_project_snippet! | |
7 | + | |
8 | + # Allow write(create) snippet | |
9 | + before_filter :authorize_write_project_snippet!, only: [:new, :create] | |
10 | + | |
11 | + # Allow modify snippet | |
12 | + before_filter :authorize_modify_project_snippet!, only: [:edit, :update] | |
13 | + | |
14 | + # Allow destroy snippet | |
15 | + before_filter :authorize_admin_project_snippet!, only: [:destroy] | |
16 | + | |
17 | + layout 'project_resource' | |
18 | + | |
19 | + respond_to :html | |
20 | + | |
21 | + def index | |
22 | + @snippets = @project.snippets.fresh.non_expired | |
23 | + end | |
24 | + | |
25 | + def new | |
26 | + @snippet = @project.snippets.build | |
27 | + end | |
28 | + | |
29 | + def create | |
30 | + @snippet = @project.snippets.build(params[:project_snippet]) | |
31 | + @snippet.author = current_user | |
32 | + | |
33 | + if @snippet.save | |
34 | + redirect_to project_snippet_path(@project, @snippet) | |
35 | + else | |
36 | + respond_with(@snippet) | |
37 | + end | |
38 | + end | |
39 | + | |
40 | + def edit | |
41 | + end | |
42 | + | |
43 | + def update | |
44 | + if @snippet.update_attributes(params[:project_snippet]) | |
45 | + redirect_to project_snippet_path(@project, @snippet) | |
46 | + else | |
47 | + respond_with(@snippet) | |
48 | + end | |
49 | + end | |
50 | + | |
51 | + def show | |
52 | + @note = @project.notes.new(noteable: @snippet) | |
53 | + @target_type = :snippet | |
54 | + @target_id = @snippet.id | |
55 | + end | |
56 | + | |
57 | + def destroy | |
58 | + return access_denied! unless can?(current_user, :admin_project_snippet, @snippet) | |
59 | + | |
60 | + @snippet.destroy | |
61 | + | |
62 | + redirect_to project_snippets_path(@project) | |
63 | + end | |
64 | + | |
65 | + def raw | |
66 | + send_data( | |
67 | + @snippet.content, | |
68 | + type: "text/plain", | |
69 | + disposition: 'inline', | |
70 | + filename: @snippet.file_name | |
71 | + ) | |
72 | + end | |
73 | + | |
74 | + protected | |
75 | + | |
76 | + def snippet | |
77 | + @snippet ||= @project.snippets.find(params[:id]) | |
78 | + end | |
79 | + | |
80 | + def authorize_modify_project_snippet! | |
81 | + return render_404 unless can?(current_user, :modify_project_snippet, @snippet) | |
82 | + end | |
83 | + | |
84 | + def authorize_admin_project_snippet! | |
85 | + return render_404 unless can?(current_user, :admin_project_snippet, @snippet) | |
86 | + end | |
87 | + | |
88 | + def module_enabled | |
89 | + return render_404 unless @project.snippets_enabled | |
90 | + end | |
91 | +end | ... | ... |
app/controllers/projects/teams_controller.rb
1 | 1 | class Projects::TeamsController < Projects::ApplicationController |
2 | 2 | |
3 | + before_filter :authorize_admin_team_member! | |
4 | + | |
3 | 5 | def available |
4 | 6 | @teams = current_user.is_admin? ? UserTeam.scoped : current_user.user_teams |
5 | 7 | @teams = @teams.without_project(project) |
... | ... | @@ -24,4 +26,9 @@ class Projects::TeamsController < Projects::ApplicationController |
24 | 26 | redirect_to project_team_index_path(project) |
25 | 27 | end |
26 | 28 | |
29 | + protected | |
30 | + | |
31 | + def user_team | |
32 | + @team ||= UserTeam.find_by_path(params[:id]) | |
33 | + end | |
27 | 34 | end | ... | ... |
app/controllers/snippets_controller.rb
1 | -class SnippetsController < ProjectResourceController | |
2 | - before_filter :module_enabled | |
1 | +class SnippetsController < ApplicationController | |
3 | 2 | before_filter :snippet, only: [:show, :edit, :destroy, :update, :raw] |
4 | 3 | |
5 | - # Allow read any snippet | |
6 | - before_filter :authorize_read_snippet! | |
7 | - | |
8 | - # Allow write(create) snippet | |
9 | - before_filter :authorize_write_snippet!, only: [:new, :create] | |
10 | - | |
11 | 4 | # Allow modify snippet |
12 | 5 | before_filter :authorize_modify_snippet!, only: [:edit, :update] |
13 | 6 | |
... | ... | @@ -17,22 +10,38 @@ class SnippetsController < ProjectResourceController |
17 | 10 | respond_to :html |
18 | 11 | |
19 | 12 | def index |
20 | - @snippets = @project.snippets.fresh.non_expired | |
13 | + @snippets = Snippet.public.fresh.non_expired.page(params[:page]).per(20) | |
14 | + end | |
15 | + | |
16 | + def user_index | |
17 | + @user = User.find_by_username(params[:username]) | |
18 | + | |
19 | + @snippets = @current_user.snippets.fresh.non_expired | |
20 | + | |
21 | + @snippets = case params[:scope] | |
22 | + when 'public' then | |
23 | + @snippets.public | |
24 | + when 'private' then | |
25 | + @snippets.private | |
26 | + else | |
27 | + @snippets | |
28 | + end | |
29 | + | |
30 | + @snippets = @snippets.page(params[:page]).per(20) | |
21 | 31 | end |
22 | 32 | |
23 | 33 | def new |
24 | - @snippet = @project.snippets.new | |
34 | + @snippet = PersonalSnippet.new | |
25 | 35 | end |
26 | 36 | |
27 | 37 | def create |
28 | - @snippet = @project.snippets.new(params[:snippet]) | |
38 | + @snippet = PersonalSnippet.new(params[:personal_snippet]) | |
29 | 39 | @snippet.author = current_user |
30 | - @snippet.save | |
31 | 40 | |
32 | - if @snippet.valid? | |
33 | - redirect_to [@project, @snippet] | |
41 | + if @snippet.save | |
42 | + redirect_to snippet_path(@snippet) | |
34 | 43 | else |
35 | - respond_with(@snippet) | |
44 | + respond_with @snippet | |
36 | 45 | end |
37 | 46 | end |
38 | 47 | |
... | ... | @@ -40,27 +49,22 @@ class SnippetsController < ProjectResourceController |
40 | 49 | end |
41 | 50 | |
42 | 51 | def update |
43 | - @snippet.update_attributes(params[:snippet]) | |
44 | - | |
45 | - if @snippet.valid? | |
46 | - redirect_to [@project, @snippet] | |
52 | + if @snippet.update_attributes(params[:personal_snippet]) | |
53 | + redirect_to snippet_path(@snippet) | |
47 | 54 | else |
48 | - respond_with(@snippet) | |
55 | + respond_with @snippet | |
49 | 56 | end |
50 | 57 | end |
51 | 58 | |
52 | 59 | def show |
53 | - @note = @project.notes.new(noteable: @snippet) | |
54 | - @target_type = :snippet | |
55 | - @target_id = @snippet.id | |
56 | 60 | end |
57 | 61 | |
58 | 62 | def destroy |
59 | - return access_denied! unless can?(current_user, :admin_snippet, @snippet) | |
63 | + return access_denied! unless can?(current_user, :admin_personal_snippet, @snippet) | |
60 | 64 | |
61 | 65 | @snippet.destroy |
62 | 66 | |
63 | - redirect_to project_snippets_path(@project) | |
67 | + redirect_to snippets_path | |
64 | 68 | end |
65 | 69 | |
66 | 70 | def raw |
... | ... | @@ -75,18 +79,14 @@ class SnippetsController < ProjectResourceController |
75 | 79 | protected |
76 | 80 | |
77 | 81 | def snippet |
78 | - @snippet ||= @project.snippets.find(params[:id]) | |
82 | + @snippet ||= PersonalSnippet.find(params[:id]) | |
79 | 83 | end |
80 | 84 | |
81 | 85 | def authorize_modify_snippet! |
82 | - return render_404 unless can?(current_user, :modify_snippet, @snippet) | |
86 | + return render_404 unless can?(current_user, :modify_personal_snippet, @snippet) | |
83 | 87 | end |
84 | 88 | |
85 | 89 | def authorize_admin_snippet! |
86 | - return render_404 unless can?(current_user, :admin_snippet, @snippet) | |
87 | - end | |
88 | - | |
89 | - def module_enabled | |
90 | - return render_404 unless @project.snippets_enabled | |
90 | + return render_404 unless can?(current_user, :admin_personal_snippet, @snippet) | |
91 | 91 | end |
92 | 92 | end | ... | ... |
app/helpers/tab_helper.rb
... | ... | @@ -73,7 +73,7 @@ module TabHelper |
73 | 73 | end |
74 | 74 | |
75 | 75 | def project_tab_class |
76 | - return "active" if current_page?(controller: "projects", action: :edit, id: @project) | |
76 | + return "active" if current_page?(controller: "/projects", action: :edit, id: @project) | |
77 | 77 | |
78 | 78 | if ['services', 'hooks', 'deploy_keys', 'team_members'].include? controller.controller_name |
79 | 79 | "active" | ... | ... |
app/models/ability.rb
... | ... | @@ -7,7 +7,8 @@ class Ability |
7 | 7 | when "Project" then project_abilities(user, subject) |
8 | 8 | when "Issue" then issue_abilities(user, subject) |
9 | 9 | when "Note" then note_abilities(user, subject) |
10 | - when "Snippet" then snippet_abilities(user, subject) | |
10 | + when "ProjectSnippet" then project_snippet_abilities(user, subject) | |
11 | + when "PersonalSnippet" then personal_snippet_abilities(user, subject) | |
11 | 12 | when "MergeRequest" then merge_request_abilities(user, subject) |
12 | 13 | when "Group", "Namespace" then group_abilities(user, subject) |
13 | 14 | when "UserTeam" then user_team_abilities(user, subject) |
... | ... | @@ -54,7 +55,7 @@ class Ability |
54 | 55 | :read_wiki, |
55 | 56 | :read_issue, |
56 | 57 | :read_milestone, |
57 | - :read_snippet, | |
58 | + :read_project_snippet, | |
58 | 59 | :read_team_member, |
59 | 60 | :read_merge_request, |
60 | 61 | :read_note, |
... | ... | @@ -67,8 +68,8 @@ class Ability |
67 | 68 | def project_report_rules |
68 | 69 | project_guest_rules + [ |
69 | 70 | :download_code, |
70 | - :write_snippet, | |
71 | - :fork_project | |
71 | + :fork_project, | |
72 | + :write_project_snippet | |
72 | 73 | ] |
73 | 74 | end |
74 | 75 | |
... | ... | @@ -84,11 +85,11 @@ class Ability |
84 | 85 | project_dev_rules + [ |
85 | 86 | :push_code_to_protected_branches, |
86 | 87 | :modify_issue, |
87 | - :modify_snippet, | |
88 | + :modify_project_snippet, | |
88 | 89 | :modify_merge_request, |
89 | 90 | :admin_issue, |
90 | 91 | :admin_milestone, |
91 | - :admin_snippet, | |
92 | + :admin_project_snippet, | |
92 | 93 | :admin_team_member, |
93 | 94 | :admin_merge_request, |
94 | 95 | :admin_note, |
... | ... | @@ -135,8 +136,7 @@ class Ability |
135 | 136 | rules.flatten |
136 | 137 | end |
137 | 138 | |
138 | - | |
139 | - [:issue, :note, :snippet, :merge_request].each do |name| | |
139 | + [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name| | |
140 | 140 | define_method "#{name}_abilities" do |user, subject| |
141 | 141 | if subject.author == user |
142 | 142 | [ | ... | ... |
app/models/event.rb
app/models/note.rb
... | ... | @@ -159,4 +159,10 @@ class Note < ActiveRecord::Base |
159 | 159 | "wall" |
160 | 160 | end |
161 | 161 | end |
162 | + | |
163 | + # FIXME: Hack for polymorphic associations with STI | |
164 | + # For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations | |
165 | + def noteable_type=(sType) | |
166 | + super(sType.to_s.classify.constantize.base_class.to_s) | |
167 | + end | |
162 | 168 | end | ... | ... |
... | ... | @@ -0,0 +1,18 @@ |
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: snippets | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# title :string(255) | |
7 | +# content :text | |
8 | +# author_id :integer not null | |
9 | +# project_id :integer not null | |
10 | +# created_at :datetime not null | |
11 | +# updated_at :datetime not null | |
12 | +# file_name :string(255) | |
13 | +# expires_at :datetime | |
14 | +# type :string(255) | |
15 | +# private :boolean | |
16 | + | |
17 | +class PersonalSnippet < Snippet | |
18 | +end | ... | ... |
app/models/project.rb
... | ... | @@ -57,7 +57,7 @@ class Project < ActiveRecord::Base |
57 | 57 | has_many :milestones, dependent: :destroy |
58 | 58 | has_many :users_projects, dependent: :destroy |
59 | 59 | has_many :notes, dependent: :destroy |
60 | - has_many :snippets, dependent: :destroy | |
60 | + has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" | |
61 | 61 | has_many :hooks, dependent: :destroy, class_name: "ProjectHook" |
62 | 62 | has_many :protected_branches, dependent: :destroy |
63 | 63 | has_many :user_team_project_relationships, dependent: :destroy | ... | ... |
... | ... | @@ -0,0 +1,27 @@ |
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: snippets | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# title :string(255) | |
7 | +# content :text | |
8 | +# author_id :integer not null | |
9 | +# project_id :integer not null | |
10 | +# created_at :datetime not null | |
11 | +# updated_at :datetime not null | |
12 | +# file_name :string(255) | |
13 | +# expires_at :datetime | |
14 | +# type :string(255) | |
15 | +# private :boolean | |
16 | + | |
17 | +class ProjectSnippet < Snippet | |
18 | + belongs_to :project | |
19 | + belongs_to :author, class_name: "User" | |
20 | + | |
21 | + validates :project, presence: true | |
22 | + | |
23 | + # Scopes | |
24 | + scope :fresh, -> { order("created_at DESC") } | |
25 | + scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } | |
26 | + scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } | |
27 | +end | ... | ... |
app/models/snippet.rb
... | ... | @@ -11,29 +11,31 @@ |
11 | 11 | # updated_at :datetime not null |
12 | 12 | # file_name :string(255) |
13 | 13 | # expires_at :datetime |
14 | -# | |
14 | +# type :string(255) | |
15 | +# private :boolean | |
15 | 16 | |
16 | 17 | class Snippet < ActiveRecord::Base |
17 | 18 | include Linguist::BlobHelper |
18 | 19 | |
19 | - attr_accessible :title, :content, :file_name, :expires_at | |
20 | + attr_accessible :title, :content, :file_name, :expires_at, :private | |
20 | 21 | |
21 | - belongs_to :project | |
22 | 22 | belongs_to :author, class_name: "User" |
23 | + | |
23 | 24 | has_many :notes, as: :noteable, dependent: :destroy |
24 | 25 | |
25 | 26 | delegate :name, :email, to: :author, prefix: true, allow_nil: true |
26 | 27 | |
27 | 28 | validates :author, presence: true |
28 | - validates :project, presence: true | |
29 | 29 | validates :title, presence: true, length: { within: 0..255 } |
30 | 30 | validates :file_name, presence: true, length: { within: 0..255 } |
31 | 31 | validates :content, presence: true |
32 | 32 | |
33 | 33 | # Scopes |
34 | - scope :fresh, -> { order("created_at DESC") } | |
35 | - scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } | |
34 | + scope :public, -> { where(private: false) } | |
35 | + scope :private, -> { where(private: true) } | |
36 | + scope :fresh, -> { order("created_at DESC") } | |
36 | 37 | scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } |
38 | + scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } | |
37 | 39 | |
38 | 40 | def self.content_types |
39 | 41 | [ | ... | ... |
app/models/user.rb
... | ... | @@ -78,6 +78,7 @@ class User < ActiveRecord::Base |
78 | 78 | has_many :team_projects, through: :user_team_project_relationships |
79 | 79 | |
80 | 80 | # Projects |
81 | + has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" | |
81 | 82 | has_many :users_projects, dependent: :destroy |
82 | 83 | has_many :issues, dependent: :destroy, foreign_key: :author_id |
83 | 84 | has_many :notes, dependent: :destroy, foreign_key: :author_id | ... | ... |
app/views/events/event/_note.html.haml
... | ... | @@ -5,6 +5,10 @@ |
5 | 5 | - if event.note_commit? |
6 | 6 | = event.note_target_type |
7 | 7 | = link_to event.note_short_commit_id, project_commit_path(event.project, event.note_commit_id), class: "commit_short_id" |
8 | + - if event.note_project_snippet? | |
9 | + = link_to project_snippet_path(event.project, event.note_target) do | |
10 | + %strong | |
11 | + #{event.note_target_type} ##{truncate event.note_target_id} | |
8 | 12 | - else |
9 | 13 | = link_to [event.project, event.note_target] do |
10 | 14 | %strong | ... | ... |
app/views/layouts/_head_panel.html.haml
... | ... | @@ -18,6 +18,9 @@ |
18 | 18 | %li |
19 | 19 | = link_to public_root_path, title: "Public area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do |
20 | 20 | %i.icon-globe |
21 | + %li | |
22 | + = link_to snippets_path, title: "Snippets area", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do | |
23 | + %i.icon-paste | |
21 | 24 | - if current_user.is_admin? |
22 | 25 | %li |
23 | 26 | = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do | ... | ... |
... | ... | @@ -0,0 +1,23 @@ |
1 | +!!! 5 | |
2 | +%html{ lang: "en"} | |
3 | + = render "layouts/head", title: "Snipepts" | |
4 | + %body{class: "#{app_theme} application"} | |
5 | + = render "layouts/head_panel", title: "Snippets" | |
6 | + = render "layouts/flash" | |
7 | + %nav.main-nav | |
8 | + .container | |
9 | + %ul | |
10 | + = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do | |
11 | + = link_to root_path, title: "Back to dashboard" do | |
12 | + %i.icon-home | |
13 | + = nav_link(path: 'snippet#new') do | |
14 | + = link_to new_snippet_path do | |
15 | + New snippet | |
16 | + = nav_link(path: 'snippets#user_index') do | |
17 | + = link_to user_snippets_path(@current_user) do | |
18 | + My snippets | |
19 | + = nav_link(path: 'snippets#index') do | |
20 | + = link_to snippets_path do | |
21 | + Discover snippets | |
22 | + .container | |
23 | + .content= yield | ... | ... |
... | ... | @@ -0,0 +1,12 @@ |
1 | +.file_holder | |
2 | + .file_title | |
3 | + %i.icon-file | |
4 | + %strong= @snippet.file_name | |
5 | + %span.options | |
6 | + = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank" | |
7 | + .file_content.code | |
8 | + - unless @snippet.content.empty? | |
9 | + %div{class: user_color_scheme_class} | |
10 | + = raw @snippet.colorize(formatter: :gitlab) | |
11 | + - else | |
12 | + %p.nothing_here_message Empty file | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +%h3.page_title | |
2 | + = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" | |
3 | +%hr | |
4 | +.snippet-form-holder | |
5 | + = form_for [@project, @snippet], as: :project_snippet, url: url do |f| | |
6 | + -if @snippet.errors.any? | |
7 | + .alert.alert-error | |
8 | + %ul | |
9 | + - @snippet.errors.full_messages.each do |msg| | |
10 | + %li= msg | |
11 | + | |
12 | + .clearfix | |
13 | + = f.label :title | |
14 | + .input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true | |
15 | + .clearfix | |
16 | + = f.label "Lifetime" | |
17 | + .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} | |
18 | + .clearfix | |
19 | + .file-editor | |
20 | + = f.label :file_name, "File" | |
21 | + .input | |
22 | + .file_holder.snippet | |
23 | + .file_title | |
24 | + = f.text_field :file_name, placeholder: "example.rb", class: 'snippet-file-name', required: true | |
25 | + .file_content.code | |
26 | + %pre#editor= @snippet.content | |
27 | + = f.hidden_field :content, class: 'snippet-file-content' | |
28 | + | |
29 | + .form-actions | |
30 | + = f.submit 'Save', class: "btn-save btn" | |
31 | + = link_to "Cancel", project_snippets_path(@project), class: " btn" | |
32 | + - unless @snippet.new_record? | |
33 | + .pull-right= link_to 'Destroy', project_snippet_path(@project, @snippet), confirm: 'Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" | |
34 | + | |
35 | + | |
36 | +:javascript | |
37 | + var editor = ace.edit("editor"); | |
38 | + $(".snippet-form-holder form").submit(function(){ | |
39 | + $(".snippet-file-content").val(editor.getValue()); | |
40 | + }); | |
41 | + | ... | ... |
... | ... | @@ -0,0 +1,13 @@ |
1 | +%tr | |
2 | + %td | |
3 | + = image_tag gravatar_icon(snippet.author_email), class: "avatar s24" | |
4 | + %a{href: project_snippet_path(snippet.project, snippet)} | |
5 | + %strong= truncate(snippet.title, length: 60) | |
6 | + %td | |
7 | + = snippet.file_name | |
8 | + %td | |
9 | + %span.cgray | |
10 | + - if snippet.expires_at | |
11 | + = snippet.expires_at.to_date.to_s(:short) | |
12 | + - else | |
13 | + Never | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | += render "projects/snippets/form", url: project_snippet_path(@project, @snippet) | ... | ... |
... | ... | @@ -0,0 +1,19 @@ |
1 | +%h3.page_title | |
2 | + Snippets | |
3 | + %small share code pastes with others out of git repository | |
4 | + | |
5 | + - if can? current_user, :write_project_snippet, @project | |
6 | + = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do | |
7 | + Add new snippet | |
8 | +%br | |
9 | +%table | |
10 | + %thead | |
11 | + %tr | |
12 | + %th Title | |
13 | + %th File Name | |
14 | + %th Expires At | |
15 | + = render partial: "projects/snippets/snippet", collection: @snippets | |
16 | + - if @snippets.empty? | |
17 | + %tr | |
18 | + %td{colspan: 3} | |
19 | + %h3.nothing_here_message Nothing here. | ... | ... |
... | ... | @@ -0,0 +1 @@ |
1 | += render "projects/snippets/form", url: project_snippets_path(@project, @snippet) | ... | ... |
... | ... | @@ -0,0 +1,9 @@ |
1 | +%h3.page_title | |
2 | + = @snippet.title | |
3 | + %small= @snippet.file_name | |
4 | + - if can?(current_user, :admin_project_snippet, @project) || @snippet.author == current_user | |
5 | + = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small pull-right", title: 'Edit Snippet' | |
6 | + | |
7 | +%br | |
8 | +%div= render 'projects/snippets/blob' | |
9 | +%div#notes= render "notes/notes_with_form" | ... | ... |
app/views/snippets/_blob.html.haml
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 | %i.icon-file |
4 | 4 | %strong= @snippet.file_name |
5 | 5 | %span.options |
6 | - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-tiny", target: "_blank" | |
6 | + = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-tiny", target: "_blank" | |
7 | 7 | .file_content.code |
8 | 8 | - unless @snippet.content.empty? |
9 | 9 | %div{class: user_color_scheme_class} | ... | ... |
app/views/snippets/_form.html.haml
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | = @snippet.new_record? ? "New Snippet" : "Edit Snippet ##{@snippet.id}" |
3 | 3 | %hr |
4 | 4 | .snippet-form-holder |
5 | - = form_for [@project, @snippet] do |f| | |
5 | + = form_for @snippet, as: :personal_snippet, url: url do |f| | |
6 | 6 | -if @snippet.errors.any? |
7 | 7 | .alert.alert-error |
8 | 8 | %ul |
... | ... | @@ -13,6 +13,9 @@ |
13 | 13 | = f.label :title |
14 | 14 | .input= f.text_field :title, placeholder: "Example Snippet", class: 'input-xlarge', required: true |
15 | 15 | .clearfix |
16 | + = f.label "Private?" | |
17 | + .input= f.check_box :private, {class: ''} | |
18 | + .clearfix | |
16 | 19 | = f.label "Lifetime" |
17 | 20 | .input= f.select :expires_at, lifetime_select_options, {}, {class: 'chosen span2'} |
18 | 21 | .clearfix |
... | ... | @@ -28,9 +31,9 @@ |
28 | 31 | |
29 | 32 | .form-actions |
30 | 33 | = f.submit 'Save', class: "btn-save btn" |
31 | - = link_to "Cancel", project_snippets_path(@project), class: " btn" | |
34 | + = link_to "Cancel", snippets_path(@project), class: " btn" | |
32 | 35 | - unless @snippet.new_record? |
33 | - .pull-right= link_to 'Destroy', [@project, @snippet], confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" | |
36 | + .pull-right= link_to 'Destroy', snippet_path(@snippet), confirm: 'Removed snippet cannot be restored! Are you sure?', method: :delete, class: "btn pull-right danger delete-snippet", id: "destroy_snippet_#{@snippet.id}" | |
34 | 37 | |
35 | 38 | |
36 | 39 | :javascript | ... | ... |
app/views/snippets/_snippet.html.haml
1 | 1 | %tr |
2 | 2 | %td |
3 | + - if snippet.private? | |
4 | + %i.icon-lock | |
5 | + - else | |
6 | + %i.icon-globe | |
3 | 7 | = image_tag gravatar_icon(snippet.author_email), class: "avatar s24" |
4 | - %a{href: project_snippet_path(snippet.project, snippet)} | |
5 | - %strong= truncate(snippet.title, length: 60) | |
8 | + - if snippet.project_id? | |
9 | + %a{href: project_snippet_path(snippet.project, snippet)} | |
10 | + %strong= truncate(snippet.title, length: 60) | |
11 | + - else | |
12 | + %a{href: snippet_path(snippet)} | |
13 | + %strong= truncate(snippet.title, length: 60) | |
6 | 14 | %td |
7 | 15 | = snippet.file_name |
8 | 16 | %td |
... | ... | @@ -11,3 +19,6 @@ |
11 | 19 | = snippet.expires_at.to_date.to_s(:short) |
12 | 20 | - else |
13 | 21 | Never |
22 | + %td | |
23 | + - if snippet.project_id? | |
24 | + = link_to snippet.project.name, project_path(snippet.project) | ... | ... |
... | ... | @@ -0,0 +1,15 @@ |
1 | +%table | |
2 | + %thead | |
3 | + %tr | |
4 | + %th Title | |
5 | + %th File Name | |
6 | + %th Expires At | |
7 | + %th Project | |
8 | + | |
9 | + = render partial: 'snippet', collection: @snippets | |
10 | + - if @snippets.empty? | |
11 | + %tr | |
12 | + %td{colspan: 4} | |
13 | + %h3.nothing_here_message Nothing here. | |
14 | + | |
15 | += paginate @snippets | ... | ... |
app/views/snippets/edit.html.haml
app/views/snippets/index.html.haml
1 | 1 | %h3.page_title |
2 | - Snippets | |
2 | + Public snippets | |
3 | 3 | %small share code pastes with others out of git repository |
4 | + = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do | |
5 | + Add new snippet | |
6 | + | |
7 | +%hr | |
8 | +.row | |
9 | + .span12 | |
10 | + = render 'snippets' | |
4 | 11 | |
5 | - - if can? current_user, :write_snippet, @project | |
6 | - = link_to new_project_snippet_path(@project), class: "btn btn-small add_new pull-right", title: "New Snippet" do | |
7 | - Add new snippet | |
8 | -%br | |
9 | -%table | |
10 | - %thead | |
11 | - %tr | |
12 | - %th Title | |
13 | - %th File Name | |
14 | - %th Expires At | |
15 | - = render @snippets | |
16 | - - if @snippets.empty? | |
17 | - %tr | |
18 | - %td{colspan: 3} | |
19 | - %h3.nothing_here_message Nothing here. | ... | ... |
app/views/snippets/new.html.haml
app/views/snippets/show.html.haml
1 | 1 | %h3.page_title |
2 | + - if @snippet.private? | |
3 | + %i.icon-lock | |
4 | + - else | |
5 | + %i.icon-globe | |
6 | + | |
2 | 7 | = @snippet.title |
3 | 8 | %small= @snippet.file_name |
4 | - - if can?(current_user, :admin_snippet, @project) || @snippet.author == current_user | |
5 | - = link_to "Edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small pull-right", title: 'Edit Snippet' | |
9 | + - if @snippet.author == current_user | |
10 | + = link_to "Edit", edit_snippet_path(@snippet), class: "btn btn-small pull-right", title: 'Edit Snippet' | |
6 | 11 | |
7 | 12 | %br |
8 | 13 | %div= render 'blob' |
9 | -%div#notes= render "notes/notes_with_form" | ... | ... |
... | ... | @@ -0,0 +1,20 @@ |
1 | +%h3.page_title | |
2 | + Snippets by | |
3 | + = @user.name | |
4 | + %small share code pastes with others out of git repository | |
5 | + = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do | |
6 | + Add new snippet | |
7 | + | |
8 | +%hr | |
9 | +.row | |
10 | + .span3 | |
11 | + %ul.nav.nav-pills.nav-stacked | |
12 | + = nav_tab :scope, nil do | |
13 | + = link_to "All", user_snippets_path(@user) | |
14 | + = nav_tab :scope, 'private' do | |
15 | + = link_to "Private", user_snippets_path(@user, scope: 'private') | |
16 | + = nav_tab :scope, 'public' do | |
17 | + = link_to "Public", user_snippets_path(@user, scope: 'public') | |
18 | + | |
19 | + .span9 | |
20 | + = render 'snippets' | ... | ... |
config/routes.rb
... | ... | @@ -39,6 +39,16 @@ Gitlab::Application.routes.draw do |
39 | 39 | get 'help/workflow' => 'help#workflow' |
40 | 40 | |
41 | 41 | # |
42 | + # Global snippets | |
43 | + # | |
44 | + resources :snippets do | |
45 | + member do | |
46 | + get "raw" | |
47 | + end | |
48 | + end | |
49 | + get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ } | |
50 | + | |
51 | + # | |
42 | 52 | # Public namespace |
43 | 53 | # |
44 | 54 | namespace :public do |
... | ... | @@ -182,6 +192,14 @@ Gitlab::Application.routes.draw do |
182 | 192 | resources :graph, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} |
183 | 193 | match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/} |
184 | 194 | |
195 | + scope module: :projects do | |
196 | + resources :snippets do | |
197 | + member do | |
198 | + get "raw" | |
199 | + end | |
200 | + end | |
201 | + end | |
202 | + | |
185 | 203 | resources :wikis, only: [:show, :edit, :destroy, :create] do |
186 | 204 | collection do |
187 | 205 | get :pages |
... | ... | @@ -255,19 +273,12 @@ Gitlab::Application.routes.draw do |
255 | 273 | end |
256 | 274 | end |
257 | 275 | |
258 | - resources :snippets do | |
259 | - member do | |
260 | - get "raw" | |
261 | - end | |
262 | - end | |
263 | - | |
264 | 276 | resources :hooks, only: [:index, :create, :destroy] do |
265 | 277 | member do |
266 | 278 | get :test |
267 | 279 | end |
268 | 280 | end |
269 | 281 | |
270 | - | |
271 | 282 | resources :team, controller: 'team_members', only: [:index] |
272 | 283 | resources :milestones, except: [:destroy] |
273 | 284 | ... | ... |
db/migrate/20130324172327_change_project_id_to_null_in_snipepts.rb
0 → 100644
db/migrate/20130324203535_add_type_value_for_snippets.rb
0 → 100644
db/schema.rb
... | ... | @@ -203,12 +203,14 @@ ActiveRecord::Schema.define(:version => 20130522141856) do |
203 | 203 | create_table "snippets", :force => true do |t| |
204 | 204 | t.string "title" |
205 | 205 | t.text "content" |
206 | - t.integer "author_id", :null => false | |
207 | - t.integer "project_id", :null => false | |
208 | - t.datetime "created_at", :null => false | |
209 | - t.datetime "updated_at", :null => false | |
206 | + t.integer "author_id", :null => false | |
207 | + t.integer "project_id" | |
208 | + t.datetime "created_at", :null => false | |
209 | + t.datetime "updated_at", :null => false | |
210 | 210 | t.string "file_name" |
211 | 211 | t.datetime "expires_at" |
212 | + t.boolean "private", :default => true, :null => false | |
213 | + t.string "type" | |
212 | 214 | end |
213 | 215 | |
214 | 216 | add_index "snippets", ["created_at"], :name => "index_snippets_on_created_at" | ... | ... |
... | ... | @@ -0,0 +1,35 @@ |
1 | +Feature: Project Snippets | |
2 | + Background: | |
3 | + Given I sign in as a user | |
4 | + And I own project "Shop" | |
5 | + And project "Shop" have "Snippet one" snippet | |
6 | + And project "Shop" have no "Snippet two" snippet | |
7 | + And I visit project "Shop" snippets page | |
8 | + | |
9 | + Scenario: I should see snippets | |
10 | + Given I visit project "Shop" snippets page | |
11 | + Then I should see "Snippet one" in snippets | |
12 | + And I should not see "Snippet two" in snippets | |
13 | + | |
14 | + Scenario: I create new project snippet | |
15 | + Given I click link "New Snippet" | |
16 | + And I submit new snippet "Snippet three" | |
17 | + Then I should see snippet "Snippet three" | |
18 | + | |
19 | + @javascript | |
20 | + Scenario: I comment on a snippet "Snippet one" | |
21 | + Given I visit snippet page "Snippet one" | |
22 | + And I leave a comment like "Good snippet!" | |
23 | + Then I should see comment "Good snippet!" | |
24 | + | |
25 | + Scenario: I update "Snippet one" | |
26 | + Given I visit snippet page "Snippet one" | |
27 | + And I click link "Edit" | |
28 | + And I submit new title "Snippet new title" | |
29 | + Then I should see "Snippet new title" | |
30 | + | |
31 | + Scenario: I destroy "Snippet one" | |
32 | + Given I visit snippet page "Snippet one" | |
33 | + And I click link "Edit" | |
34 | + And I click link "Destroy" | |
35 | + Then I should not see "Snippet one" in snippets | ... | ... |
... | ... | @@ -0,0 +1,10 @@ |
1 | +Feature: Discover Snippets | |
2 | + Background: | |
3 | + Given I sign in as a user | |
4 | + And I have public "Personal snippet one" snippet | |
5 | + And I have private "Personal snippet private" snippet | |
6 | + | |
7 | + Scenario: I should see snippets | |
8 | + Given I visit snippets page | |
9 | + Then I should see "Personal snippet one" in snippets | |
10 | + And I should not see "Personal snippet private" in snippets | ... | ... |
... | ... | @@ -0,0 +1,28 @@ |
1 | +Feature: Snippets Feature | |
2 | + Background: | |
3 | + Given I sign in as a user | |
4 | + And I have public "Personal snippet one" snippet | |
5 | + And I have private "Personal snippet private" snippet | |
6 | + | |
7 | + Scenario: I create new snippet | |
8 | + Given I visit new snippet page | |
9 | + And I submit new snippet "Personal snippet three" | |
10 | + Then I should see snippet "Personal snippet three" | |
11 | + | |
12 | + Scenario: I update "Personal snippet one" | |
13 | + Given I visit snippet page "Personal snippet one" | |
14 | + And I click link "Edit" | |
15 | + And I submit new title "Personal snippet new title" | |
16 | + Then I should see "Personal snippet new title" | |
17 | + | |
18 | + Scenario: Set "Personal snippet one" public | |
19 | + Given I visit snippet page "Personal snippet one" | |
20 | + And I click link "Edit" | |
21 | + And I uncheck "Private" checkbox | |
22 | + Then I should see "Personal snippet one" public | |
23 | + | |
24 | + Scenario: I destroy "Personal snippet one" | |
25 | + Given I visit snippet page "Personal snippet one" | |
26 | + And I click link "Edit" | |
27 | + And I click link "Destroy" | |
28 | + Then I should not see "Personal snippet one" in snippets | ... | ... |
... | ... | @@ -0,0 +1,22 @@ |
1 | +Feature: User Snippets | |
2 | + Background: | |
3 | + Given I sign in as a user | |
4 | + And I have public "Personal snippet one" snippet | |
5 | + And I have private "Personal snippet private" snippet | |
6 | + | |
7 | + Scenario: I should see all my snippets | |
8 | + Given I visit my snippets page | |
9 | + Then I should see "Personal snippet one" in snippets | |
10 | + And I should see "Personal snippet private" in snippets | |
11 | + | |
12 | + Scenario: I can see only my private snippets | |
13 | + Given I visit my snippets page | |
14 | + And I click "Private" filter | |
15 | + Then I should not see "Personal snippet one" in snippets | |
16 | + And I should see "Personal snippet private" in snippets | |
17 | + | |
18 | + Scenario: I can see only my public snippets | |
19 | + Given I visit my snippets page | |
20 | + And I click "Public" filter | |
21 | + Then I should see "Personal snippet one" in snippets | |
22 | + And I should not see "Personal snippet private" in snippets | ... | ... |
... | ... | @@ -0,0 +1,100 @@ |
1 | +class ProjectSnippets < Spinach::FeatureSteps | |
2 | + include SharedAuthentication | |
3 | + include SharedProject | |
4 | + include SharedNote | |
5 | + include SharedPaths | |
6 | + | |
7 | + And 'project "Shop" have "Snippet one" snippet' do | |
8 | + create(:project_snippet, | |
9 | + title: "Snippet one", | |
10 | + content: "Test content", | |
11 | + file_name: "snippet.rb", | |
12 | + project: project, | |
13 | + author: project.users.first) | |
14 | + end | |
15 | + | |
16 | + And 'project "Shop" have no "Snippet two" snippet' do | |
17 | + create(:snippet, | |
18 | + title: "Snippet two", | |
19 | + content: "Test content", | |
20 | + file_name: "snippet.rb", | |
21 | + author: project.users.first) | |
22 | + end | |
23 | + | |
24 | + Given 'I click link "New Snippet"' do | |
25 | + click_link "Add new snippet" | |
26 | + end | |
27 | + | |
28 | + Given 'I click link "Snippet one"' do | |
29 | + click_link "Snippet one" | |
30 | + end | |
31 | + | |
32 | + Then 'I should see "Snippet one" in snippets' do | |
33 | + page.should have_content "Snippet one" | |
34 | + end | |
35 | + | |
36 | + And 'I should not see "Snippet two" in snippets' do | |
37 | + page.should_not have_content "Snippet two" | |
38 | + end | |
39 | + | |
40 | + And 'I should not see "Snippet one" in snippets' do | |
41 | + page.should_not have_content "Snippet one" | |
42 | + end | |
43 | + | |
44 | + And 'I click link "Edit"' do | |
45 | + within ".page_title" do | |
46 | + click_link "Edit" | |
47 | + end | |
48 | + end | |
49 | + | |
50 | + And 'I click link "Destroy"' do | |
51 | + click_link "Destroy" | |
52 | + end | |
53 | + | |
54 | + And 'I submit new snippet "Snippet three"' do | |
55 | + fill_in "project_snippet_title", :with => "Snippet three" | |
56 | + select "forever", :from => "project_snippet_expires_at" | |
57 | + fill_in "project_snippet_file_name", :with => "my_snippet.rb" | |
58 | + within('.file-editor') do | |
59 | + find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three' | |
60 | + end | |
61 | + click_button "Save" | |
62 | + end | |
63 | + | |
64 | + Then 'I should see snippet "Snippet three"' do | |
65 | + page.should have_content "Snippet three" | |
66 | + page.should have_content "Content of snippet three" | |
67 | + end | |
68 | + | |
69 | + And 'I submit new title "Snippet new title"' do | |
70 | + fill_in "project_snippet_title", :with => "Snippet new title" | |
71 | + click_button "Save" | |
72 | + end | |
73 | + | |
74 | + Then 'I should see "Snippet new title"' do | |
75 | + page.should have_content "Snippet new title" | |
76 | + end | |
77 | + | |
78 | + And 'I leave a comment like "Good snippet!"' do | |
79 | + within('.js-main-target-form') do | |
80 | + fill_in "note_note", with: "Good snippet!" | |
81 | + click_button "Add Comment" | |
82 | + end | |
83 | + end | |
84 | + | |
85 | + Then 'I should see comment "Good snippet!"' do | |
86 | + page.should have_content "Good snippet!" | |
87 | + end | |
88 | + | |
89 | + And 'I visit snippet page "Snippet one"' do | |
90 | + visit project_snippet_path(project, project_snippet) | |
91 | + end | |
92 | + | |
93 | + def project | |
94 | + @project ||= Project.find_by_name!("Shop") | |
95 | + end | |
96 | + | |
97 | + def project_snippet | |
98 | + @project_snippet ||= ProjectSnippet.find_by_title!("Snippet One") | |
99 | + end | |
100 | +end | ... | ... |
features/steps/shared/paths.rb
... | ... | @@ -275,6 +275,22 @@ module SharedPaths |
275 | 275 | visit public_root_path |
276 | 276 | end |
277 | 277 | |
278 | + # ---------------------------------------- | |
279 | + # Snippets | |
280 | + # ---------------------------------------- | |
281 | + | |
282 | + Given 'I visit project "Shop" snippets page' do | |
283 | + visit project_snippets_path(project) | |
284 | + end | |
285 | + | |
286 | + Given 'I visit snippets page' do | |
287 | + visit snippets_path | |
288 | + end | |
289 | + | |
290 | + Given 'I visit new snippet page' do | |
291 | + visit new_snippet_path | |
292 | + end | |
293 | + | |
278 | 294 | def root_ref |
279 | 295 | @project.repository.root_ref |
280 | 296 | end | ... | ... |
... | ... | @@ -0,0 +1,21 @@ |
1 | +module SharedSnippet | |
2 | + include Spinach::DSL | |
3 | + | |
4 | + And 'I have public "Personal snippet one" snippet' do | |
5 | + create(:personal_snippet, | |
6 | + title: "Personal snippet one", | |
7 | + content: "Test content", | |
8 | + file_name: "snippet.rb", | |
9 | + private: false, | |
10 | + author: current_user) | |
11 | + end | |
12 | + | |
13 | + And 'I have private "Personal snippet private" snippet' do | |
14 | + create(:personal_snippet, | |
15 | + title: "Personal snippet private", | |
16 | + content: "Provate content", | |
17 | + file_name: "private_snippet.rb", | |
18 | + private: true, | |
19 | + author: current_user) | |
20 | + end | |
21 | +end | ... | ... |
... | ... | @@ -0,0 +1,17 @@ |
1 | +class DiscoverSnippets < Spinach::FeatureSteps | |
2 | + include SharedAuthentication | |
3 | + include SharedPaths | |
4 | + include SharedSnippet | |
5 | + | |
6 | + Then 'I should see "Personal snippet one" in snippets' do | |
7 | + page.should have_content "Personal snippet one" | |
8 | + end | |
9 | + | |
10 | + And 'I should not see "Personal snippet private" in snippets' do | |
11 | + page.should_not have_content "Personal snippet private" | |
12 | + end | |
13 | + | |
14 | + def snippet | |
15 | + @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one") | |
16 | + end | |
17 | +end | ... | ... |
... | ... | @@ -0,0 +1,65 @@ |
1 | +class SnippetsFeature < Spinach::FeatureSteps | |
2 | + include SharedAuthentication | |
3 | + include SharedPaths | |
4 | + include SharedProject | |
5 | + include SharedSnippet | |
6 | + | |
7 | + Given 'I click link "Personal snippet one"' do | |
8 | + click_link "Personal snippet one" | |
9 | + end | |
10 | + | |
11 | + And 'I should not see "Personal snippet one" in snippets' do | |
12 | + page.should_not have_content "Personal snippet one" | |
13 | + end | |
14 | + | |
15 | + And 'I click link "Edit"' do | |
16 | + within ".page_title" do | |
17 | + click_link "Edit" | |
18 | + end | |
19 | + end | |
20 | + | |
21 | + And 'I click link "Destroy"' do | |
22 | + click_link "Destroy" | |
23 | + end | |
24 | + | |
25 | + And 'I submit new snippet "Personal snippet three"' do | |
26 | + fill_in "personal_snippet_title", :with => "Personal snippet three" | |
27 | + select "forever", :from => "personal_snippet_expires_at" | |
28 | + fill_in "personal_snippet_file_name", :with => "my_snippet.rb" | |
29 | + within('.file-editor') do | |
30 | + find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of snippet three' | |
31 | + end | |
32 | + click_button "Save" | |
33 | + end | |
34 | + | |
35 | + Then 'I should see snippet "Personal snippet three"' do | |
36 | + page.should have_content "Personal snippet three" | |
37 | + page.should have_content "Content of snippet three" | |
38 | + end | |
39 | + | |
40 | + And 'I submit new title "Personal snippet new title"' do | |
41 | + fill_in "personal_snippet_title", :with => "Personal snippet new title" | |
42 | + click_button "Save" | |
43 | + end | |
44 | + | |
45 | + Then 'I should see "Personal snippet new title"' do | |
46 | + page.should have_content "Personal snippet new title" | |
47 | + end | |
48 | + | |
49 | + And 'I uncheck "Private" checkbox' do | |
50 | + find(:xpath, "//input[@id='personal_snippet_private']").set true | |
51 | + click_button "Save" | |
52 | + end | |
53 | + | |
54 | + Then 'I should see "Personal snippet one" public' do | |
55 | + page.should have_no_xpath("//i[@class='public-snippet']") | |
56 | + end | |
57 | + | |
58 | + And 'I visit snippet page "Personal snippet one"' do | |
59 | + visit snippet_path(snippet) | |
60 | + end | |
61 | + | |
62 | + def snippet | |
63 | + @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one") | |
64 | + end | |
65 | +end | ... | ... |
... | ... | @@ -0,0 +1,41 @@ |
1 | +class UserSnippets < Spinach::FeatureSteps | |
2 | + include SharedAuthentication | |
3 | + include SharedPaths | |
4 | + include SharedSnippet | |
5 | + | |
6 | + Given 'I visit my snippets page' do | |
7 | + visit user_snippets_path(current_user) | |
8 | + end | |
9 | + | |
10 | + Then 'I should see "Personal snippet one" in snippets' do | |
11 | + page.should have_content "Personal snippet one" | |
12 | + end | |
13 | + | |
14 | + And 'I should see "Personal snippet private" in snippets' do | |
15 | + page.should have_content "Personal snippet private" | |
16 | + end | |
17 | + | |
18 | + Then 'I should not see "Personal snippet one" in snippets' do | |
19 | + page.should_not have_content "Personal snippet one" | |
20 | + end | |
21 | + | |
22 | + And 'I should not see "Personal snippet private" in snippets' do | |
23 | + page.should_not have_content "Personal snippet private" | |
24 | + end | |
25 | + | |
26 | + Given 'I click "Public" filter' do | |
27 | + within('.nav-stacked') do | |
28 | + click_link "Public" | |
29 | + end | |
30 | + end | |
31 | + | |
32 | + Given 'I click "Private" filter' do | |
33 | + within('.nav-stacked') do | |
34 | + click_link "Private" | |
35 | + end | |
36 | + end | |
37 | + | |
38 | + def snippet | |
39 | + @snippet ||= PersonalSnippet.find_by_title!("Personal snippet one") | |
40 | + end | |
41 | +end | ... | ... |
lib/api/projects.rb
... | ... | @@ -328,7 +328,7 @@ module API |
328 | 328 | # Example Request: |
329 | 329 | # POST /projects/:id/snippets |
330 | 330 | post ":id/snippets" do |
331 | - authorize! :write_snippet, user_project | |
331 | + authorize! :write_project_snippet, user_project | |
332 | 332 | required_attributes! [:title, :file_name, :code] |
333 | 333 | |
334 | 334 | attrs = attributes_for_keys [:title, :file_name] |
... | ... | @@ -357,7 +357,7 @@ module API |
357 | 357 | # PUT /projects/:id/snippets/:snippet_id |
358 | 358 | put ":id/snippets/:snippet_id" do |
359 | 359 | @snippet = user_project.snippets.find(params[:snippet_id]) |
360 | - authorize! :modify_snippet, @snippet | |
360 | + authorize! :modify_project_snippet, @snippet | |
361 | 361 | |
362 | 362 | attrs = attributes_for_keys [:title, :file_name] |
363 | 363 | attrs[:expires_at] = params[:lifetime] if params[:lifetime].present? |
... | ... | @@ -380,7 +380,7 @@ module API |
380 | 380 | delete ":id/snippets/:snippet_id" do |
381 | 381 | begin |
382 | 382 | @snippet = user_project.snippets.find(params[:snippet_id]) |
383 | - authorize! :modify_snippet, user_project | |
383 | + authorize! :modify_project_snippet, @snippet | |
384 | 384 | @snippet.destroy |
385 | 385 | rescue |
386 | 386 | end | ... | ... |
spec/factories.rb
... | ... | @@ -197,7 +197,7 @@ FactoryGirl.define do |
197 | 197 | url |
198 | 198 | end |
199 | 199 | |
200 | - factory :snippet do | |
200 | + factory :project_snippet do | |
201 | 201 | project |
202 | 202 | author |
203 | 203 | title |
... | ... | @@ -205,6 +205,20 @@ FactoryGirl.define do |
205 | 205 | file_name |
206 | 206 | end |
207 | 207 | |
208 | + factory :personal_snippet do | |
209 | + author | |
210 | + title | |
211 | + content | |
212 | + file_name | |
213 | + end | |
214 | + | |
215 | + factory :snippet do | |
216 | + author | |
217 | + title | |
218 | + content | |
219 | + file_name | |
220 | + end | |
221 | + | |
208 | 222 | factory :protected_branch do |
209 | 223 | name |
210 | 224 | project | ... | ... |
spec/features/snippets_spec.rb
... | ... | @@ -1,99 +0,0 @@ |
1 | -require 'spec_helper' | |
2 | - | |
3 | -describe "Snippets" do | |
4 | - let(:project) { create(:project) } | |
5 | - | |
6 | - before do | |
7 | - login_as :user | |
8 | - project.team << [@user, :developer] | |
9 | - end | |
10 | - | |
11 | - describe "GET /snippets" do | |
12 | - before do | |
13 | - @snippet = create(:snippet, | |
14 | - author: @user, | |
15 | - project: project) | |
16 | - | |
17 | - visit project_snippets_path(project) | |
18 | - end | |
19 | - | |
20 | - subject { page } | |
21 | - | |
22 | - it { should have_content(@snippet.title[0..10]) } | |
23 | - it { should have_content(@snippet.project.name) } | |
24 | - | |
25 | - describe "Destroy" do | |
26 | - before do | |
27 | - # admin access to remove snippet | |
28 | - @user.users_projects.destroy_all | |
29 | - project.team << [@user, :master] | |
30 | - visit edit_project_snippet_path(project, @snippet) | |
31 | - end | |
32 | - | |
33 | - it "should remove entry" do | |
34 | - expect { | |
35 | - click_link "destroy_snippet_#{@snippet.id}" | |
36 | - }.to change { Snippet.count }.by(-1) | |
37 | - end | |
38 | - end | |
39 | - end | |
40 | - | |
41 | - describe "New snippet" do | |
42 | - before do | |
43 | - visit project_snippets_path(project) | |
44 | - click_link "New Snippet" | |
45 | - end | |
46 | - | |
47 | - it "should open new snippet popup" do | |
48 | - page.current_path.should == new_project_snippet_path(project) | |
49 | - end | |
50 | - | |
51 | - describe "fill in", js: true do | |
52 | - before do | |
53 | - fill_in "snippet_title", with: "login function" | |
54 | - fill_in "snippet_file_name", with: "test.rb" | |
55 | - page.execute_script("editor.insert('def login; end');") | |
56 | - end | |
57 | - | |
58 | - it { expect { click_button "Save" }.to change {Snippet.count}.by(1) } | |
59 | - | |
60 | - it "should add new snippet to table" do | |
61 | - click_button "Save" | |
62 | - page.current_path.should == project_snippet_path(project, Snippet.last) | |
63 | - page.should have_content "login function" | |
64 | - page.should have_content "test.rb" | |
65 | - end | |
66 | - end | |
67 | - end | |
68 | - | |
69 | - describe "Edit snippet" do | |
70 | - before do | |
71 | - @snippet = create(:snippet, | |
72 | - author: @user, | |
73 | - project: project) | |
74 | - visit project_snippet_path(project, @snippet) | |
75 | - click_link "Edit Snippet" | |
76 | - end | |
77 | - | |
78 | - it "should open edit page" do | |
79 | - page.current_path.should == edit_project_snippet_path(project, @snippet) | |
80 | - end | |
81 | - | |
82 | - describe "fill in" do | |
83 | - before do | |
84 | - fill_in "snippet_title", with: "login function" | |
85 | - fill_in "snippet_file_name", with: "test.rb" | |
86 | - end | |
87 | - | |
88 | - it { expect { click_button "Save" }.to_not change {Snippet.count} } | |
89 | - | |
90 | - it "should update snippet fields" do | |
91 | - click_button "Save" | |
92 | - | |
93 | - page.current_path.should == project_snippet_path(project, @snippet) | |
94 | - page.should have_content "login function" | |
95 | - page.should have_content "test.rb" | |
96 | - end | |
97 | - end | |
98 | - end | |
99 | -end |
spec/helpers/gitlab_markdown_helper_spec.rb
... | ... | @@ -10,7 +10,7 @@ describe GitlabMarkdownHelper do |
10 | 10 | let(:commit) { project.repository.commit } |
11 | 11 | let(:issue) { create(:issue, project: project) } |
12 | 12 | let(:merge_request) { create(:merge_request, project: project) } |
13 | - let(:snippet) { create(:snippet, project: project) } | |
13 | + let(:snippet) { create(:project_snippet, project: project) } | |
14 | 14 | let(:member) { project.users_projects.where(user_id: user).first } |
15 | 15 | |
16 | 16 | before do |
... | ... | @@ -190,8 +190,43 @@ describe GitlabMarkdownHelper do |
190 | 190 | describe "referencing a snippet" do |
191 | 191 | let(:object) { snippet } |
192 | 192 | let(:reference) { "$#{snippet.id}" } |
193 | + let(:actual) { "Reference to #{reference}" } | |
194 | + let(:expected) { project_snippet_path(project, object) } | |
195 | + | |
196 | + it "should link using a valid id" do | |
197 | + gfm(actual).should match(expected) | |
198 | + end | |
199 | + | |
200 | + it "should link with adjacent text" do | |
201 | + # Wrap the reference in parenthesis | |
202 | + gfm(actual.gsub(reference, "(#{reference})")).should match(expected) | |
203 | + | |
204 | + # Append some text to the end of the reference | |
205 | + gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected) | |
206 | + end | |
207 | + | |
208 | + it "should keep whitespace intact" do | |
209 | + actual = "Referenced #{reference} already." | |
210 | + expected = /Referenced <a.+>[^\s]+<\/a> already/ | |
211 | + gfm(actual).should match(expected) | |
212 | + end | |
213 | + | |
214 | + it "should not link with an invalid id" do | |
215 | + # Modify the reference string so it's still parsed, but is invalid | |
216 | + reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2)) | |
217 | + gfm(actual).should == actual | |
218 | + end | |
219 | + | |
220 | + it "should include a title attribute" do | |
221 | + title = "Snippet: #{object.title}" | |
222 | + gfm(actual).should match(/title="#{title}"/) | |
223 | + end | |
224 | + | |
225 | + it "should include standard gfm classes" do | |
226 | + css = object.class.to_s.underscore | |
227 | + gfm(actual).should match(/class="\s?gfm gfm-snippet\s?"/) | |
228 | + end | |
193 | 229 | |
194 | - include_examples 'referenced object' | |
195 | 230 | end |
196 | 231 | |
197 | 232 | describe "referencing multiple objects" do | ... | ... |
... | ... | @@ -0,0 +1,30 @@ |
1 | +# == Schema Information | |
2 | +# | |
3 | +# Table name: snippets | |
4 | +# | |
5 | +# id :integer not null, primary key | |
6 | +# title :string(255) | |
7 | +# content :text | |
8 | +# author_id :integer not null | |
9 | +# project_id :integer not null | |
10 | +# created_at :datetime not null | |
11 | +# updated_at :datetime not null | |
12 | +# file_name :string(255) | |
13 | +# expires_at :datetime | |
14 | +# | |
15 | + | |
16 | +require 'spec_helper' | |
17 | + | |
18 | +describe ProjectSnippet do | |
19 | + describe "Associations" do | |
20 | + it { should belong_to(:project) } | |
21 | + end | |
22 | + | |
23 | + describe "Mass assignment" do | |
24 | + it { should_not allow_mass_assignment_of(:project_id) } | |
25 | + end | |
26 | + | |
27 | + describe "Validation" do | |
28 | + it { should validate_presence_of(:project) } | |
29 | + end | |
30 | +end | ... | ... |
spec/models/project_spec.rb
... | ... | @@ -36,7 +36,7 @@ describe Project do |
36 | 36 | it { should have_many(:milestones).dependent(:destroy) } |
37 | 37 | it { should have_many(:users_projects).dependent(:destroy) } |
38 | 38 | it { should have_many(:notes).dependent(:destroy) } |
39 | - it { should have_many(:snippets).dependent(:destroy) } | |
39 | + it { should have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) } | |
40 | 40 | it { should have_many(:deploy_keys_projects).dependent(:destroy) } |
41 | 41 | it { should have_many(:deploy_keys) } |
42 | 42 | it { should have_many(:hooks).dependent(:destroy) } | ... | ... |
spec/models/snippet_spec.rb
... | ... | @@ -17,19 +17,16 @@ require 'spec_helper' |
17 | 17 | |
18 | 18 | describe Snippet do |
19 | 19 | describe "Associations" do |
20 | - it { should belong_to(:project) } | |
21 | 20 | it { should belong_to(:author).class_name('User') } |
22 | 21 | it { should have_many(:notes).dependent(:destroy) } |
23 | 22 | end |
24 | 23 | |
25 | 24 | describe "Mass assignment" do |
26 | 25 | it { should_not allow_mass_assignment_of(:author_id) } |
27 | - it { should_not allow_mass_assignment_of(:project_id) } | |
28 | 26 | end |
29 | 27 | |
30 | 28 | describe "Validation" do |
31 | 29 | it { should validate_presence_of(:author) } |
32 | - it { should validate_presence_of(:project) } | |
33 | 30 | |
34 | 31 | it { should validate_presence_of(:title) } |
35 | 32 | it { should ensure_length_of(:title).is_within(0..255) } | ... | ... |
spec/models/user_spec.rb
... | ... | @@ -41,6 +41,7 @@ require 'spec_helper' |
41 | 41 | describe User do |
42 | 42 | describe "Associations" do |
43 | 43 | it { should have_one(:namespace) } |
44 | + it { should have_many(:snippets).class_name('Snippet').dependent(:destroy) } | |
44 | 45 | it { should have_many(:users_projects).dependent(:destroy) } |
45 | 46 | it { should have_many(:groups) } |
46 | 47 | it { should have_many(:keys).dependent(:destroy) } | ... | ... |
spec/requests/api/notes_spec.rb
... | ... | @@ -7,7 +7,7 @@ describe API::API do |
7 | 7 | let!(:project) { create(:project, namespace: user.namespace ) } |
8 | 8 | let!(:issue) { create(:issue, project: project, author: user) } |
9 | 9 | let!(:merge_request) { create(:merge_request, project: project, author: user) } |
10 | - let!(:snippet) { create(:snippet, project: project, author: user) } | |
10 | + let!(:snippet) { create(:project_snippet, project: project, author: user) } | |
11 | 11 | let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } |
12 | 12 | let!(:merge_request_note) { create(:note, noteable: merge_request, project: project, author: user) } |
13 | 13 | let!(:snippet_note) { create(:note, noteable: snippet, project: project, author: user) } | ... | ... |
spec/requests/api/projects_spec.rb
... | ... | @@ -10,7 +10,7 @@ describe API::API do |
10 | 10 | let(:admin) { create(:admin) } |
11 | 11 | let!(:project) { create(:project_with_code, creator_id: user.id) } |
12 | 12 | let!(:hook) { create(:project_hook, project: project, url: "http://example.com") } |
13 | - let!(:snippet) { create(:snippet, author: user, project: project, title: 'example') } | |
13 | + let!(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') } | |
14 | 14 | let!(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } |
15 | 15 | let!(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } |
16 | 16 | ... | ... |
spec/routing/project_routing_spec.rb
... | ... | @@ -258,13 +258,37 @@ end |
258 | 258 | # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show |
259 | 259 | # PUT /:project_id/snippets/:id(.:format) snippets#update |
260 | 260 | # DELETE /:project_id/snippets/:id(.:format) snippets#destroy |
261 | -describe SnippetsController, "routing" do | |
261 | +describe Project::SnippetsController, "routing" do | |
262 | 262 | it "to #raw" do |
263 | - get("/gitlabhq/snippets/1/raw").should route_to('snippets#raw', project_id: 'gitlabhq', id: '1') | |
263 | + get("/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlabhq', id: '1') | |
264 | 264 | end |
265 | 265 | |
266 | - it_behaves_like "RESTful project resources" do | |
267 | - let(:controller) { 'snippets' } | |
266 | + it "to #index" do | |
267 | + get("/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlabhq') | |
268 | + end | |
269 | + | |
270 | + it "to #create" do | |
271 | + post("/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlabhq') | |
272 | + end | |
273 | + | |
274 | + it "to #new" do | |
275 | + get("/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlabhq') | |
276 | + end | |
277 | + | |
278 | + it "to #edit" do | |
279 | + get("/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlabhq', id: '1') | |
280 | + end | |
281 | + | |
282 | + it "to #show" do | |
283 | + get("/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlabhq', id: '1') | |
284 | + end | |
285 | + | |
286 | + it "to #update" do | |
287 | + put("/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlabhq', id: '1') | |
288 | + end | |
289 | + | |
290 | + it "to #destroy" do | |
291 | + delete("/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlabhq', id: '1') | |
268 | 292 | end |
269 | 293 | end |
270 | 294 | ... | ... |
spec/routing/routing_spec.rb
... | ... | @@ -19,6 +19,51 @@ describe "Mounted Apps", "routing" do |
19 | 19 | end |
20 | 20 | end |
21 | 21 | |
22 | +# snippets GET /snippets(.:format) snippets#index | |
23 | +# POST /snippets(.:format) snippets#create | |
24 | +# new_snippet GET /snippets/new(.:format) snippets#new | |
25 | +# edit_snippet GET /snippets/:id/edit(.:format) snippets#edit | |
26 | +# snippet GET /snippets/:id(.:format) snippets#show | |
27 | +# PUT /snippets/:id(.:format) snippets#update | |
28 | +# DELETE /snippets/:id(.:format) snippets#destroy | |
29 | +describe SnippetsController, "routing" do | |
30 | + it "to #user_index" do | |
31 | + get("/s/User").should route_to('snippets#user_index', username: 'User') | |
32 | + end | |
33 | + | |
34 | + it "to #raw" do | |
35 | + get("/snippets/1/raw").should route_to('snippets#raw', id: '1') | |
36 | + end | |
37 | + | |
38 | + it "to #index" do | |
39 | + get("/snippets").should route_to('snippets#index') | |
40 | + end | |
41 | + | |
42 | + it "to #create" do | |
43 | + post("/snippets").should route_to('snippets#create') | |
44 | + end | |
45 | + | |
46 | + it "to #new" do | |
47 | + get("/snippets/new").should route_to('snippets#new') | |
48 | + end | |
49 | + | |
50 | + it "to #edit" do | |
51 | + get("/snippets/1/edit").should route_to('snippets#edit', id: '1') | |
52 | + end | |
53 | + | |
54 | + it "to #show" do | |
55 | + get("/snippets/1").should route_to('snippets#show', id: '1') | |
56 | + end | |
57 | + | |
58 | + it "to #update" do | |
59 | + put("/snippets/1").should route_to('snippets#update', id: '1') | |
60 | + end | |
61 | + | |
62 | + it "to #destroy" do | |
63 | + delete("/snippets/1").should route_to('snippets#destroy', id: '1') | |
64 | + end | |
65 | +end | |
66 | + | |
22 | 67 | # help GET /help(.:format) help#index |
23 | 68 | # help_permissions GET /help/permissions(.:format) help#permissions |
24 | 69 | # help_workflow GET /help/workflow(.:format) help#workflow | ... | ... |