From fde754e2676e40dcf2600190983ef54030c5d5a5 Mon Sep 17 00:00:00 2001 From: Guyzmo Date: Sat, 26 Nov 2016 16:37:26 +0100 Subject: API: Endpoint to expose personal snippets as /snippets Adding the necessary API for the new /snippets Restful resource added with this commit. Added a new Grape class `Snippets`, as well as a `PersonalSnippet` entity. Issue: #20042 Merge-Request: !6373 Signed-off-by: Guyzmo --- app/finders/snippets_finder.rb | 5 +- app/helpers/gitlab_routing_helper.rb | 5 + app/policies/personal_snippet_policy.rb | 5 + changelogs/unreleased/features-api-snippets.yml | 4 + doc/api/snippets.md | 232 ++++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/entities.rb | 13 ++ lib/api/snippets.rb | 137 ++++++++++++++ lib/gitlab/url_builder.rb | 2 + spec/finders/snippets_finder_spec.rb | 59 +++--- spec/requests/api/snippets_spec.rb | 157 ++++++++++++++++ 11 files changed, 594 insertions(+), 26 deletions(-) create mode 100644 changelogs/unreleased/features-api-snippets.yml create mode 100644 doc/api/snippets.md create mode 100644 lib/api/snippets.rb create mode 100644 spec/requests/api/snippets_spec.rb diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 00ff1611039..0586a923a74 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -1,12 +1,15 @@ class SnippetsFinder def execute(current_user, params = {}) filter = params[:filter] + user = params.fetch(:user, current_user) case filter when :all then snippets(current_user).fresh + when :public then + Snippet.are_public.fresh when :by_user then - by_user(current_user, params[:user], params[:scope]) + by_user(current_user, user, params[:scope]) when :by_project by_project(current_user, params[:project]) end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index af9087d8326..99db73c9ee0 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -159,6 +159,11 @@ module GitlabRoutingHelper resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) end + # Snippets + def personal_snippet_url(snippet, *args) + snippet_url(snippet) + end + # Groups ## Members diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb index 46c5aa1a5be..d3913986cd8 100644 --- a/app/policies/personal_snippet_policy.rb +++ b/app/policies/personal_snippet_policy.rb @@ -6,9 +6,14 @@ class PersonalSnippetPolicy < BasePolicy if @subject.author == @user can! :read_personal_snippet can! :update_personal_snippet + can! :destroy_personal_snippet can! :admin_personal_snippet end + unless @user.external? + can! :create_personal_snippet + end + if @subject.internal? && !@user.external? can! :read_personal_snippet end diff --git a/changelogs/unreleased/features-api-snippets.yml b/changelogs/unreleased/features-api-snippets.yml new file mode 100644 index 00000000000..80c7bb75359 --- /dev/null +++ b/changelogs/unreleased/features-api-snippets.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Endpoint to expose personal snippets as /snippets' +merge_request: 6373 +author: Bernard Guyzmo Pratz diff --git a/doc/api/snippets.md b/doc/api/snippets.md new file mode 100644 index 00000000000..5a5dc162ffe --- /dev/null +++ b/doc/api/snippets.md @@ -0,0 +1,232 @@ +# Snippets + +> [Introduced][ce-6373] in GitLab 8.15. + +### Snippet visibility level + +Snippets in GitLab can be either private, internal, or public. +You can set it with the `visibility_level` field in the snippet. + +Constants for snippet visibility levels are: + +| Visibility | Visibility level | Description | +| ---------- | ---------------- | ----------- | +| Private | `0` | The snippet is visible only to the snippet creator | +| Internal | `10` | The snippet is visible for any logged in user | +| Public | `20` | The snippet can be accessed without any authentication | + +## List snippets + +Get a list of current user's snippets. + +``` +GET /snippets +``` + +## Single snippet + +Get a single snippet. + +``` +GET /snippets/:id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | Integer | yes | The ID of a snippet | + +``` bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets/1 +``` + +Example response: + +``` json +{ + "id": 1, + "title": "test", + "file_name": "add.rb", + "author": { + "id": 1, + "username": "john_smith", + "email": "john@example.com", + "name": "John Smith", + "state": "active", + "created_at": "2012-05-23T08:00:58Z" + }, + "expires_at": null, + "updated_at": "2012-06-28T10:52:04Z", + "created_at": "2012-06-28T10:52:04Z", + "web_url": "http://example.com/snippets/1", +} +``` + +## Create new snippet + +Creates a new snippet. The user must have permission to create new snippets. + +``` +POST /snippets +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `title` | String | yes | The title of a snippet | +| `file_name` | String | yes | The name of a snippet file | +| `content` | String | yes | The content of a snippet | +| `visibility_level` | Integer | yes | The snippet's visibility | + + +``` bash +curl --request POST --data '{"title": "This is a snippet", "content": "Hello world", "file_name": "test.txt", "visibility_level": 10 }' --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets +``` + +Example response: + +``` json +{ + "id": 1, + "title": "This is a snippet", + "file_name": "test.txt", + "author": { + "id": 1, + "username": "john_smith", + "email": "john@example.com", + "name": "John Smith", + "state": "active", + "created_at": "2012-05-23T08:00:58Z" + }, + "expires_at": null, + "updated_at": "2012-06-28T10:52:04Z", + "created_at": "2012-06-28T10:52:04Z", + "web_url": "http://example.com/snippets/1", +} +``` + +## Update snippet + +Updates an existing snippet. The user must have permission to change an existing snippet. + +``` +PUT /snippets/:id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | Integer | yes | The ID of a snippet | +| `title` | String | no | The title of a snippet | +| `file_name` | String | no | The name of a snippet file | +| `content` | String | no | The content of a snippet | +| `visibility_level` | Integer | no | The snippet's visibility | + + +``` bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data '{"title": "foo", "content": "bar"}' https://gitlab.example.com/api/v3/snippets/1 +``` + +Example response: + +``` json +{ + "id": 1, + "title": "test", + "file_name": "add.rb", + "author": { + "id": 1, + "username": "john_smith", + "email": "john@example.com", + "name": "John Smith", + "state": "active", + "created_at": "2012-05-23T08:00:58Z" + }, + "expires_at": null, + "updated_at": "2012-06-28T10:52:04Z", + "created_at": "2012-06-28T10:52:04Z", + "web_url": "http://example.com/snippets/1", +} +``` + +## Delete snippet + +Deletes an existing snippet. + +``` +DELETE /snippets/:id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | Integer | yes | The ID of a snippet | + + +``` +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/snippets/1" +``` + +upon successful delete a `204 No content` HTTP code shall be expected, with no data, +but if the snippet is non-existent, a `404 Not Found` will be returned. + +## Explore all public snippets + +``` +GET /snippets/public +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `per_page` | Integer | no | number of snippets to return per page | +| `page` | Integer | no | the page to retrieve | + +``` bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/snippets/public?per_page=2&page=1 +``` + +Example response: + +``` json +[ + { + "author": { + "avatar_url": "http://www.gravatar.com/avatar/edaf55a9e363ea263e3b981d09e0f7f7?s=80&d=identicon", + "id": 12, + "name": "Libby Rolfson", + "state": "active", + "username": "elton_wehner", + "web_url": "http://localhost:3000/elton_wehner" + }, + "created_at": "2016-11-25T16:53:34.504Z", + "file_name": "oconnerrice.rb", + "id": 49, + "raw_url": "http://localhost:3000/snippets/49/raw", + "title": "Ratione cupiditate et laborum temporibus.", + "updated_at": "2016-11-25T16:53:34.504Z", + "web_url": "http://localhost:3000/snippets/49" + }, + { + "author": { + "avatar_url": "http://www.gravatar.com/avatar/36583b28626de71061e6e5a77972c3bd?s=80&d=identicon", + "id": 16, + "name": "Llewellyn Flatley", + "state": "active", + "username": "adaline", + "web_url": "http://localhost:3000/adaline" + }, + "created_at": "2016-11-25T16:53:34.479Z", + "file_name": "muellershields.rb", + "id": 48, + "raw_url": "http://localhost:3000/snippets/48/raw", + "title": "Minus similique nesciunt vel fugiat qui ullam sunt.", + "updated_at": "2016-11-25T16:53:34.479Z", + "web_url": "http://localhost:3000/snippets/48" + } +] +``` + diff --git a/lib/api/api.rb b/lib/api/api.rb index 67109ceeef9..cec2702e44d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -64,6 +64,7 @@ module API mount ::API::Session mount ::API::Settings mount ::API::SidekiqMetrics + mount ::API::Snippets mount ::API::Subscriptions mount ::API::SystemHooks mount ::API::Tags diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d5dfb8d00be..dc6d085925d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -200,6 +200,19 @@ module API end end + class PersonalSnippet < Grape::Entity + expose :id, :title, :file_name + expose :author, using: Entities::UserBasic + expose :updated_at, :created_at + + expose :web_url do |snippet| + Gitlab::UrlBuilder.build(snippet) + end + expose :raw_url do |snippet| + Gitlab::UrlBuilder.build(snippet) + "/raw" + end + end + class ProjectEntity < Grape::Entity expose :id, :iid expose(:project_id) { |entity| entity.project.id } diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb new file mode 100644 index 00000000000..e096e636806 --- /dev/null +++ b/lib/api/snippets.rb @@ -0,0 +1,137 @@ +module API + # Snippets API + class Snippets < Grape::API + include PaginationParams + + before { authenticate! } + + resource :snippets do + helpers do + def snippets_for_current_user + SnippetsFinder.new.execute(current_user, filter: :by_user, user: current_user) + end + + def public_snippets + SnippetsFinder.new.execute(current_user, filter: :public) + end + end + + desc 'Get a snippets list for authenticated user' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + use :pagination + end + get do + present paginate(snippets_for_current_user), with: Entities::PersonalSnippet + end + + desc 'List all public snippets current_user has access to' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + use :pagination + end + get 'public' do + present paginate(public_snippets), with: Entities::PersonalSnippet + end + + desc 'Get a single snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + get ':id' do + snippet = snippets_for_current_user.find(params[:id]) + present snippet, with: Entities::PersonalSnippet + end + + desc 'Create new snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :title, type: String, desc: 'The title of a snippet' + requires :file_name, type: String, desc: 'The name of a snippet file' + requires :content, type: String, desc: 'The content of a snippet' + optional :visibility_level, type: Integer, + values: Gitlab::VisibilityLevel.values, + default: Gitlab::VisibilityLevel::INTERNAL, + desc: 'The visibility level of the snippet' + end + post do + attrs = declared_params(include_missing: false) + snippet = CreateSnippetService.new(nil, current_user, attrs).execute + + if snippet.persisted? + present snippet, with: Entities::PersonalSnippet + else + render_validation_error!(snippet) + end + end + + desc 'Update an existing snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + optional :title, type: String, desc: 'The title of a snippet' + optional :file_name, type: String, desc: 'The name of a snippet file' + optional :content, type: String, desc: 'The content of a snippet' + optional :visibility_level, type: Integer, + values: Gitlab::VisibilityLevel.values, + desc: 'The visibility level of the snippet' + at_least_one_of :title, :file_name, :content, :visibility_level + end + put ':id' do + snippet = snippets_for_current_user.find_by(id: params.delete(:id)) + return not_found!('Snippet') unless snippet + authorize! :update_personal_snippet, snippet + + attrs = declared_params(include_missing: false) + + UpdateSnippetService.new(nil, current_user, snippet, attrs).execute + if snippet.persisted? + present snippet, with: Entities::PersonalSnippet + else + render_validation_error!(snippet) + end + end + + desc 'Remove snippet' do + detail 'This feature was introduced in GitLab 8.15.' + success Entities::PersonalSnippet + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + delete ':id' do + snippet = snippets_for_current_user.find_by(id: params.delete(:id)) + return not_found!('Snippet') unless snippet + authorize! :destroy_personal_snippet, snippet + snippet.destroy + no_content! + end + + desc 'Get a raw snippet' do + detail 'This feature was introduced in GitLab 8.15.' + end + params do + requires :id, type: Integer, desc: 'The ID of a snippet' + end + get ":id/raw" do + snippet = snippets_for_current_user.find_by(id: params.delete(:id)) + return not_found!('Snippet') unless snippet + + env['api.format'] = :txt + content_type 'text/plain' + present snippet.content + end + end + end +end diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index 99d0c28e749..ccb456bcc94 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -24,6 +24,8 @@ module Gitlab wiki_page_url when ProjectSnippet project_snippet_url(object) + when Snippet + personal_snippet_url(object) else raise NotImplementedError.new("No URL builder defined for #{object.class}") end diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 28bdc18e840..4427443208a 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -9,65 +9,74 @@ describe SnippetsFinder do let(:project2) { create(:empty_project, :private, group: group) } context ':all filter' do - before do - @snippet1 = create(:personal_snippet, :private) - @snippet2 = create(:personal_snippet, :internal) - @snippet3 = create(:personal_snippet, :public) - end + let!(:snippet1) { create(:personal_snippet, :private) } + let!(:snippet2) { create(:personal_snippet, :internal) } + let!(:snippet3) { create(:personal_snippet, :public) } it "returns all private and internal snippets" do snippets = SnippetsFinder.new.execute(user, filter: :all) - expect(snippets).to include(@snippet2, @snippet3) - expect(snippets).not_to include(@snippet1) + expect(snippets).to include(snippet2, snippet3) + expect(snippets).not_to include(snippet1) end it "returns all public snippets" do snippets = SnippetsFinder.new.execute(nil, filter: :all) - expect(snippets).to include(@snippet3) - expect(snippets).not_to include(@snippet1, @snippet2) + expect(snippets).to include(snippet3) + expect(snippets).not_to include(snippet1, snippet2) end end - context ':by_user filter' do - before do - @snippet1 = create(:personal_snippet, :private, author: user) - @snippet2 = create(:personal_snippet, :internal, author: user) - @snippet3 = create(:personal_snippet, :public, author: user) + context ':public filter' do + let!(:snippet1) { create(:personal_snippet, :private) } + let!(:snippet2) { create(:personal_snippet, :internal) } + let!(:snippet3) { create(:personal_snippet, :public) } + + it "returns public public snippets" do + snippets = SnippetsFinder.new.execute(nil, filter: :public) + + expect(snippets).to include(snippet3) + expect(snippets).not_to include(snippet1, snippet2) end + end + + context ':by_user filter' do + let!(:snippet1) { create(:personal_snippet, :private, author: user) } + let!(:snippet2) { create(:personal_snippet, :internal, author: user) } + let!(:snippet3) { create(:personal_snippet, :public, author: user) } it "returns all public and internal snippets" do snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user) - expect(snippets).to include(@snippet2, @snippet3) - expect(snippets).not_to include(@snippet1) + expect(snippets).to include(snippet2, snippet3) + expect(snippets).not_to include(snippet1) end it "returns internal snippets" do snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal") - expect(snippets).to include(@snippet2) - expect(snippets).not_to include(@snippet1, @snippet3) + expect(snippets).to include(snippet2) + expect(snippets).not_to include(snippet1, snippet3) end it "returns private snippets" do snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private") - expect(snippets).to include(@snippet1) - expect(snippets).not_to include(@snippet2, @snippet3) + expect(snippets).to include(snippet1) + expect(snippets).not_to include(snippet2, snippet3) end it "returns public snippets" do snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public") - expect(snippets).to include(@snippet3) - expect(snippets).not_to include(@snippet1, @snippet2) + expect(snippets).to include(snippet3) + expect(snippets).not_to include(snippet1, snippet2) end it "returns all snippets" do snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user) - expect(snippets).to include(@snippet1, @snippet2, @snippet3) + expect(snippets).to include(snippet1, snippet2, snippet3) end it "returns only public snippets if unauthenticated user" do snippets = SnippetsFinder.new.execute(nil, filter: :by_user, user: user) - expect(snippets).to include(@snippet3) - expect(snippets).not_to include(@snippet2, @snippet1) + expect(snippets).to include(snippet3) + expect(snippets).not_to include(snippet2, snippet1) end end diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb new file mode 100644 index 00000000000..f6fb6ea5506 --- /dev/null +++ b/spec/requests/api/snippets_spec.rb @@ -0,0 +1,157 @@ +require 'rails_helper' + +describe API::Snippets, api: true do + include ApiHelpers + let!(:user) { create(:user) } + + describe 'GET /snippets/' do + it 'returns snippets available' do + public_snippet = create(:personal_snippet, :public, author: user) + private_snippet = create(:personal_snippet, :private, author: user) + internal_snippet = create(:personal_snippet, :internal, author: user) + + get api("/snippets/", user) + + expect(response).to have_http_status(200) + expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( + public_snippet.id, + internal_snippet.id, + private_snippet.id) + expect(json_response.last).to have_key('web_url') + expect(json_response.last).to have_key('raw_url') + end + + it 'hides private snippets from regular user' do + create(:personal_snippet, :private) + + get api("/snippets/", user) + expect(response).to have_http_status(200) + expect(json_response.size).to eq(0) + end + end + + describe 'GET /snippets/public' do + let!(:other_user) { create(:user) } + let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + let!(:private_snippet) { create(:personal_snippet, :private, author: user) } + let!(:internal_snippet) { create(:personal_snippet, :internal, author: user) } + let!(:public_snippet_other) { create(:personal_snippet, :public, author: other_user) } + let!(:private_snippet_other) { create(:personal_snippet, :private, author: other_user) } + let!(:internal_snippet_other) { create(:personal_snippet, :internal, author: other_user) } + + it 'returns all snippets with public visibility from all users' do + get api("/snippets/public", user) + + expect(response).to have_http_status(200) + expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly( + public_snippet.id, + public_snippet_other.id) + expect(json_response.map{ |snippet| snippet['web_url']} ).to include( + "http://localhost/snippets/#{public_snippet.id}", + "http://localhost/snippets/#{public_snippet_other.id}") + expect(json_response.map{ |snippet| snippet['raw_url']} ).to include( + "http://localhost/snippets/#{public_snippet.id}/raw", + "http://localhost/snippets/#{public_snippet_other.id}/raw") + end + end + + describe 'GET /snippets/:id/raw' do + let(:snippet) { create(:personal_snippet, author: user) } + + it 'returns raw text' do + get api("/snippets/#{snippet.id}/raw", user) + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'text/plain' + expect(response.body).to eq(snippet.content) + end + + it 'returns 404 for invalid snippet id' do + delete api("/snippets/1234", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + end + + describe 'POST /snippets/' do + let(:params) do + { + title: 'Test Title', + file_name: 'test.rb', + content: 'puts "hello world"', + visibility_level: Gitlab::VisibilityLevel::PUBLIC + } + end + + it 'creates a new snippet' do + expect do + post api("/snippets/", user), params + end.to change { PersonalSnippet.count }.by(1) + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(params[:title]) + expect(json_response['file_name']).to eq(params[:file_name]) + end + + it 'returns 400 for missing parameters' do + params.delete(:title) + + post api("/snippets/", user), params + + expect(response).to have_http_status(400) + end + end + + describe 'PUT /snippets/:id' do + let(:other_user) { create(:user) } + let(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'updates snippet' do + new_content = 'New content' + + put api("/snippets/#{public_snippet.id}", user), content: new_content + + expect(response).to have_http_status(200) + public_snippet.reload + expect(public_snippet.content).to eq(new_content) + end + + it 'returns 404 for invalid snippet id' do + put api("/snippets/1234", user), title: 'foo' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + + it "returns 404 for another user's snippet" do + put api("/snippets/#{public_snippet.id}", other_user), title: 'fubar' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + + it 'returns 400 for missing parameters' do + put api("/snippets/1234", user) + + expect(response).to have_http_status(400) + end + end + + describe 'DELETE /snippets/:id' do + let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'deletes snippet' do + expect do + delete api("/snippets/#{public_snippet.id}", user) + + expect(response).to have_http_status(204) + end.to change { PersonalSnippet.count }.by(-1) + end + + it 'returns 404 for invalid snippet id' do + delete api("/snippets/1234", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Snippet Not Found') + end + end +end -- cgit v1.2.1 From c3415873b969ab3a8708da1f35388aa4f2e4155a Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sun, 4 Dec 2016 11:12:23 +0000 Subject: Instantiate ImageFile for diff-files in Diff --- app/assets/javascripts/commit/file.js | 2 +- app/assets/javascripts/commit/image_file.js | 2 +- app/assets/javascripts/diff.js.es6 | 7 +++++-- ...on-skin-controls-for-merge-request-diff-containing-an-image.yml | 4 ++++ 4 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/24949-view-2-up-swipe-onion-skin-controls-for-merge-request-diff-containing-an-image.yml diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js index 3f29826fa9b..600bac8834a 100644 --- a/app/assets/javascripts/commit/file.js +++ b/app/assets/javascripts/commit/file.js @@ -3,7 +3,7 @@ this.CommitFile = (function() { function CommitFile(file) { if ($('.image', file).length) { - new ImageFile(file); + new gl.ImageFile(file); } } diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 4c2ae595319..fd8910e916f 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,6 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, padded-blocks, max-len */ (function() { - this.ImageFile = (function() { + gl.ImageFile = (function() { var prepareFrames; // Width where images must fits in, for 2-up this gets divided by 2 diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 index ecf9d1de81c..9cf33e62958 100644 --- a/app/assets/javascripts/diff.js.es6 +++ b/app/assets/javascripts/diff.js.es6 @@ -5,8 +5,11 @@ class Diff { constructor() { - $('.files .diff-file').singleFileDiff(); - $('.files .diff-file').filesCommentButton(); + const $diffFile = $('.files .diff-file'); + $diffFile.singleFileDiff(); + $diffFile.filesCommentButton(); + + $diffFile.each((index, file) => new gl.ImageFile(file)); if (this.diffViewType() === 'parallel') { $('.content-wrapper .container-fluid').removeClass('container-limited'); diff --git a/changelogs/unreleased/24949-view-2-up-swipe-onion-skin-controls-for-merge-request-diff-containing-an-image.yml b/changelogs/unreleased/24949-view-2-up-swipe-onion-skin-controls-for-merge-request-diff-containing-an-image.yml new file mode 100644 index 00000000000..b8ba9391530 --- /dev/null +++ b/changelogs/unreleased/24949-view-2-up-swipe-onion-skin-controls-for-merge-request-diff-containing-an-image.yml @@ -0,0 +1,4 @@ +--- +title: Add image controls to MR diffs +merge_request: 7919 +author: -- cgit v1.2.1 From 7a9ba9bb85c1ab0e4bb4f116ce45b9a82aea3096 Mon Sep 17 00:00:00 2001 From: winniehell Date: Mon, 5 Dec 2016 23:02:23 +0100 Subject: Add failing test for #25191 --- spec/lib/banzai/filter/relative_link_filter_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 2bfa51deb20..df2dd173b57 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -175,7 +175,7 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw) doc = filter(image(escaped)) - expect(doc.at_css('img')['src']).to match '/raw/' + expect(doc.at_css('img')['src']).to eq "/#{project_path}/raw/#{Addressable::URI.escape(ref)}/#{escaped}" end context 'when requested path is a file in the repo' do -- cgit v1.2.1 From 186c4dd7b4f6d4edc68c6fd65703665d14877e3e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 30 Nov 2016 11:57:38 +0000 Subject: Fix broken link for latest deployment Updates changelog with MR ID --- app/helpers/environment_helper.rb | 6 ++++++ app/views/projects/builds/show.html.haml | 3 +-- .../unreleased/25136-last-deployment-link.yml | 4 ++++ spec/features/projects/builds_spec.rb | 22 ++++++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/25136-last-deployment-link.yml diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 27975b7ddb7..5fde912ab2c 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -20,6 +20,12 @@ module EnvironmentHelper link_to "##{deployment.iid}", [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] end + def last_deployment_link(deployment, link_text) + return unless deployment + + link_to link_text, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] + end + def last_deployment_link_for_environment_build(project, build) environment = environment_for_build(project, build) return unless environment diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 108674dbba6..46a1969b348 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -46,8 +46,7 @@ - else This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} - if environment.try(:last_deployment) - and will overwrite the - = link_to 'latest deployment', deployment_link(environment.last_deployment) + and will overwrite the #{last_deployment_link(environment.last_deployment, 'latest deployment')} .prepend-top-default - if @build.erased? diff --git a/changelogs/unreleased/25136-last-deployment-link.yml b/changelogs/unreleased/25136-last-deployment-link.yml new file mode 100644 index 00000000000..eab1534aa66 --- /dev/null +++ b/changelogs/unreleased/25136-last-deployment-link.yml @@ -0,0 +1,4 @@ +--- +title: Fix Latest deployment link is broken +merge_request: 7839 +author: diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index a0ccc472d11..f25a0abb33d 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -227,6 +227,28 @@ feature 'Builds', :feature do expect(page).to have_selector('.js-build-value', text: 'TRIGGER_VALUE_1') end end + + context "Build starts environment" do + context "Build is successfull and has deployment" do + it "shows a link for the build" do + -# link to environment.name + expect(page).to have_link() + end + end + + context "Build is complete and not successfull" do + it "shows a link for the build" do + -# link to environment.name + expect(page).to have_link() + end + end + + context "Build creates a new deployment" do + it "shows a link to lastest deployment" do + expect(page).to have_link("latest deployment") + end + end + end end describe "POST /:project/builds/:id/cancel" do -- cgit v1.2.1 From 19e1b3246dfe085ef604c820e3bc8263e5bffd43 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 2 Dec 2016 17:10:37 +0000 Subject: Updates tests Fix tests Fix rubocop error Fix broken test --- spec/features/projects/builds_spec.rb | 42 +++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index f25a0abb33d..2b6ad5e3420 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -228,24 +228,42 @@ feature 'Builds', :feature do end end - context "Build starts environment" do - context "Build is successfull and has deployment" do - it "shows a link for the build" do - -# link to environment.name - expect(page).to have_link() + context 'When build starts environment' do + context 'Build is successfull and has deployment' do + it 'shows a link for the build' do + environment = create(:environment, project: project) + pipeline = create(:ci_pipeline, project: project) + deployment = create(:deployment) + build1 = create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) + + visit namespace_project_build_path(project.namespace, project, build1) + + expect(page).to have_link environment.name end end - context "Build is complete and not successfull" do - it "shows a link for the build" do - -# link to environment.name - expect(page).to have_link() + context 'Build is complete and not successfull' do + it 'shows a link for the build' do + environment = create(:environment, project: project) + pipeline = create(:ci_pipeline, project: project) + build1 = create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) + + visit namespace_project_build_path(project.namespace, project, build1) + + expect(page).to have_link environment.name end end - context "Build creates a new deployment" do - it "shows a link to lastest deployment" do - expect(page).to have_link("latest deployment") + context 'Build creates a new deployment' do + it 'shows a link to lastest deployment' do + environment = create(:environment, project: project) + create(:deployment, environment: environment, sha: project.commit.id) + pipeline = create(:ci_pipeline, project: project) + build1 = create(:ci_build, :success, environment: environment.name, pipeline: pipeline) + + visit namespace_project_build_path(project.namespace, project, build1) + + expect(page).to have_link('latest deployment') end end end -- cgit v1.2.1 From 2dc907bc1761499e27c0a75eeccbbd350b38f80e Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 7 Dec 2016 10:34:00 +0000 Subject: Changes after review Fix error --- app/helpers/environment_helper.rb | 14 ++++++-------- app/views/projects/builds/show.html.haml | 2 +- spec/features/projects/builds_spec.rb | 8 ++++---- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 5fde912ab2c..96d6f64eb8e 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -14,16 +14,14 @@ module EnvironmentHelper end end - def deployment_link(deployment) + def deployment_link(deployment, text) return unless deployment - link_to "##{deployment.iid}", [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] - end - - def last_deployment_link(deployment, link_text) - return unless deployment - - link_to link_text, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] + if text + link_to text, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] + else + link_to "##{deployment.iid}", [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] + end end def last_deployment_link_for_environment_build(project, build) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 46a1969b348..3ef46872199 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -46,7 +46,7 @@ - else This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} - if environment.try(:last_deployment) - and will overwrite the #{last_deployment_link(environment.last_deployment, 'latest deployment')} + and will overwrite the #{deployment_link(environment.last_deployment, 'latest deployment')} .prepend-top-default - if @build.erased? diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index 2b6ad5e3420..ea99239d5fc 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -228,8 +228,8 @@ feature 'Builds', :feature do end end - context 'When build starts environment' do - context 'Build is successfull and has deployment' do + context 'when build starts environment' do + context 'build is successfull and has deployment' do it 'shows a link for the build' do environment = create(:environment, project: project) pipeline = create(:ci_pipeline, project: project) @@ -242,7 +242,7 @@ feature 'Builds', :feature do end end - context 'Build is complete and not successfull' do + context 'build is complete and not successfull' do it 'shows a link for the build' do environment = create(:environment, project: project) pipeline = create(:ci_pipeline, project: project) @@ -254,7 +254,7 @@ feature 'Builds', :feature do end end - context 'Build creates a new deployment' do + context 'build creates a new deployment' do it 'shows a link to lastest deployment' do environment = create(:environment, project: project) create(:deployment, environment: environment, sha: project.commit.id) -- cgit v1.2.1 From 4eb53036c7469d027d2a75aff9e70950557195d2 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 7 Dec 2016 15:27:22 +0000 Subject: Changes after review --- app/helpers/environment_helper.rb | 10 ++++------ app/views/projects/builds/show.html.haml | 2 +- spec/features/projects/builds_spec.rb | 31 ++++++++++++++----------------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 96d6f64eb8e..ff8550439d0 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -14,14 +14,12 @@ module EnvironmentHelper end end - def deployment_link(deployment, text) + def deployment_link(deployment, text: nil) return unless deployment - if text - link_to text, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] - else - link_to "##{deployment.iid}", [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] - end + link_label = text ? text : "##{deployment.iid}" + + link_to link_label, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] end def last_deployment_link_for_environment_build(project, build) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 3ef46872199..cdeb81372ee 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -46,7 +46,7 @@ - else This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} - if environment.try(:last_deployment) - and will overwrite the #{deployment_link(environment.last_deployment, 'latest deployment')} + and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')} .prepend-top-default - if @build.erased? diff --git a/spec/features/projects/builds_spec.rb b/spec/features/projects/builds_spec.rb index ea99239d5fc..8c4d4320dc5 100644 --- a/spec/features/projects/builds_spec.rb +++ b/spec/features/projects/builds_spec.rb @@ -229,39 +229,36 @@ feature 'Builds', :feature do end context 'when build starts environment' do + let(:environment) { create(:environment, project: project) } + let(:pipeline) { create(:ci_pipeline, project: project) } + context 'build is successfull and has deployment' do - it 'shows a link for the build' do - environment = create(:environment, project: project) - pipeline = create(:ci_pipeline, project: project) - deployment = create(:deployment) - build1 = create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) + let(:deployment) { create(:deployment) } + let(:build) { create(:ci_build, :success, environment: environment.name, deployments: [deployment], pipeline: pipeline) } - visit namespace_project_build_path(project.namespace, project, build1) + it 'shows a link for the build' do + visit namespace_project_build_path(project.namespace, project, build) expect(page).to have_link environment.name end end context 'build is complete and not successfull' do - it 'shows a link for the build' do - environment = create(:environment, project: project) - pipeline = create(:ci_pipeline, project: project) - build1 = create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) + let(:build) { create(:ci_build, :failed, environment: environment.name, pipeline: pipeline) } - visit namespace_project_build_path(project.namespace, project, build1) + it 'shows a link for the build' do + visit namespace_project_build_path(project.namespace, project, build) expect(page).to have_link environment.name end end context 'build creates a new deployment' do - it 'shows a link to lastest deployment' do - environment = create(:environment, project: project) - create(:deployment, environment: environment, sha: project.commit.id) - pipeline = create(:ci_pipeline, project: project) - build1 = create(:ci_build, :success, environment: environment.name, pipeline: pipeline) + let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } + let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) } - visit namespace_project_build_path(project.namespace, project, build1) + it 'shows a link to lastest deployment' do + visit namespace_project_build_path(project.namespace, project, build) expect(page).to have_link('latest deployment') end -- cgit v1.2.1 From a8cfb85a1ad5c835abe4b7cceefb9d8304717553 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sat, 3 Dec 2016 10:44:18 +0000 Subject: Added special char test to the default beforeInsert callback. I removed the quotes from the milestone displayTpl and added a skip setting for emoji instance Review changes --- app/assets/javascripts/gfm_auto_complete.js.es6 | 21 ++++---- spec/features/issues/gfm_autocomplete_spec.rb | 67 +++++++++++++++++++++---- 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 6f9d6283071..2f3da745119 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -52,6 +52,10 @@ return $.fn.atwho["default"].callbacks.filter(query, data, searchKey); }, beforeInsert: function(value) { + if (value && !this.setting.skipSpecialCharacterTest) { + var withoutAt = value.substring(1); + if (withoutAt && /[^\w\d]/.test(withoutAt)) value = value.charAt() + '"' + withoutAt + '"'; + } if (!GitLab.GfmAutoComplete.dataLoaded) { return this.at; } else { @@ -117,6 +121,7 @@ insertTpl: ':${name}:', data: ['loading'], startWithSpace: false, + skipSpecialCharacterTest: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, @@ -141,6 +146,7 @@ data: ['loading'], startWithSpace: false, alwaysHighlightFirst: true, + skipSpecialCharacterTest: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, @@ -219,12 +225,13 @@ } }; })(this), - insertTpl: '${atwho-at}"${title}"', + insertTpl: '${atwho-at}${title}', data: ['loading'], startWithSpace: false, callbacks: { matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, + beforeInsert: this.DefaultOptions.beforeInsert, beforeSave: function(milestones) { return $.map(milestones, function(m) { if (m.title == null) { @@ -284,18 +291,11 @@ callbacks: { matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, + beforeInsert: this.DefaultOptions.beforeInsert, beforeSave: function(merges) { - var sanitizeLabelTitle; - sanitizeLabelTitle = function(title) { - if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) { - return "\"" + (sanitize(title)) + "\""; - } else { - return sanitize(title); - } - }; return $.map(merges, function(m) { return { - title: sanitizeLabelTitle(m.title), + title: sanitize(m.title), color: m.color, search: "" + m.title }; @@ -308,6 +308,7 @@ at: '/', alias: 'commands', searchKey: 'search', + skipSpecialCharacterTest: true, displayTpl: function(value) { var tpl = '
  • /${name}'; if (value.aliases.length > 0) { diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index c421da97d76..cd0512a37e6 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -2,8 +2,9 @@ require 'rails_helper' feature 'GFM autocomplete', feature: true, js: true do include WaitForAjax - let(:user) { create(:user) } + let(:user) { create(:user, username: 'someone.special') } let(:project) { create(:project) } + let(:label) { create(:label, project: project, title: 'special+') } let(:issue) { create(:issue, project: project) } before do @@ -23,21 +24,69 @@ feature 'GFM autocomplete', feature: true, js: true do expect(page).to have_selector('.atwho-container') end - it 'opens autocomplete menu when field is prefixed with non-text character' do + it 'doesnt open autocomplete menu character is prefixed with text' do page.within '.timeline-content-form' do - find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('testing') find('#note_note').native.send_keys('@') end - expect(page).to have_selector('.atwho-container') + expect(page).not_to have_selector('.atwho-view') end - it 'doesnt open autocomplete menu character is prefixed with text' do - page.within '.timeline-content-form' do - find('#note_note').native.send_keys('testing') - find('#note_note').native.send_keys('@') + context 'if a selected value has special characters' do + it 'wraps the result in double quotes' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys("~#{label.title[0]}") + sleep 1 + note.click + end + + label_item = find('.atwho-view li', text: label.title) + + expect_to_wrap(true, label_item, note, label.title) end - expect(page).not_to have_selector('.atwho-view') + it 'doesn\'t wrap for assignee values' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys("@#{user.username[0]}") + sleep 1 + note.click + end + + user_item = find('.atwho-view li', text: user.username) + + expect_to_wrap(false, user_item, note, user.username) + end + + it 'doesn\'t wrap for emoji values' do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys(":cartwheel") + sleep 1 + note.click + end + + emoji_item = find('.atwho-view li', text: 'cartwheel_tone1') + + expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1') + end + + def expect_to_wrap(should_wrap, item, note, value) + expect(item).to have_content(value) + expect(item).not_to have_content("\"#{value}\"") + + item.click + + if should_wrap + expect(note.value).to include("\"#{value}\"") + else + expect(note.value).not_to include("\"#{value}\"") + end + end end end -- cgit v1.2.1 From 4a0ca85834b6cf7decb58857986e535ba4e37c53 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Tue, 6 Dec 2016 18:56:59 -0200 Subject: Allow branch names with dots on API endpoint --- changelogs/unreleased/issue_25030.yml | 4 ++++ lib/api/branches.rb | 16 ++++++++-------- spec/requests/api/branches_spec.rb | 31 +++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/issue_25030.yml diff --git a/changelogs/unreleased/issue_25030.yml b/changelogs/unreleased/issue_25030.yml new file mode 100644 index 00000000000..e18b8d6a79b --- /dev/null +++ b/changelogs/unreleased/issue_25030.yml @@ -0,0 +1,4 @@ +--- +title: Allow branch names with dots on API endpoint +merge_request: +author: diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 73aed624ea7..0950c3d2e88 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -23,9 +23,9 @@ module API success Entities::RepoBranch end params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' end - get ':id/repository/branches/:branch' do + get ':id/repository/branches/:branch', requirements: { branch: /.+/ } do branch = user_project.repository.find_branch(params[:branch]) not_found!("Branch") unless branch @@ -39,11 +39,11 @@ module API success Entities::RepoBranch end params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' optional :developers_can_push, type: Boolean, desc: 'Flag if developers can push to that branch' optional :developers_can_merge, type: Boolean, desc: 'Flag if developers can merge to that branch' end - put ':id/repository/branches/:branch/protect' do + put ':id/repository/branches/:branch/protect', requirements: { branch: /.+/ } do authorize_admin_project branch = user_project.repository.find_branch(params[:branch]) @@ -76,9 +76,9 @@ module API success Entities::RepoBranch end params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' end - put ':id/repository/branches/:branch/unprotect' do + put ':id/repository/branches/:branch/unprotect', requirements: { branch: /.+/ } do authorize_admin_project branch = user_project.repository.find_branch(params[:branch]) @@ -112,9 +112,9 @@ module API desc 'Delete a branch' params do - requires :branch, type: String, regexp: /.+/, desc: 'The name of the branch' + requires :branch, type: String, desc: 'The name of the branch' end - delete ":id/repository/branches/:branch" do + delete ":id/repository/branches/:branch", requirements: { branch: /.+/ } do authorize_push_project result = DeleteBranchService.new(user_project, current_user). diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 55b8c8c0c69..2878e0cb59b 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -11,6 +11,7 @@ describe API::Branches, api: true do let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:branch_name) { 'feature' } let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } + let!(:branch_with_dot) { CreateBranchService.new(project, user).execute("with.1.2.3", "master") } describe "GET /projects/:id/repository/branches" do it "returns an array of project branches" do @@ -37,6 +38,13 @@ describe API::Branches, api: true do expect(json_response['developers_can_merge']).to eq(false) end + it "returns the branch information for a single branch with dots in the name" do + get api("/projects/#{project.id}/repository/branches/with.1.2.3", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + end + context 'on a merged branch' do it "returns the branch information for a single branch" do get api("/projects/#{project.id}/repository/branches/merge-test", user) @@ -71,6 +79,14 @@ describe API::Branches, api: true do expect(json_response['developers_can_merge']).to eq(false) end + it "protects a single branch with dots in the name" do + put api("/projects/#{project.id}/repository/branches/with.1.2.3/protect", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + expect(json_response['protected']).to eq(true) + end + it 'protects a single branch and developers can push' do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user), developers_can_push: true @@ -220,6 +236,14 @@ describe API::Branches, api: true do expect(json_response['protected']).to eq(false) end + it "update branches with dots in branch name" do + put api("/projects/#{project.id}/repository/branches/with.1.2.3/unprotect", user) + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq("with.1.2.3") + expect(json_response['protected']).to eq(false) + end + it "returns success when unprotect branch" do put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) expect(response).to have_http_status(404) @@ -292,6 +316,13 @@ describe API::Branches, api: true do expect(json_response['branch_name']).to eq(branch_name) end + it "removes a branch with dots in the branch name" do + delete api("/projects/#{project.id}/repository/branches/with.1.2.3", user) + + expect(response).to have_http_status(200) + expect(json_response['branch_name']).to eq("with.1.2.3") + end + it 'returns 404 if branch not exists' do delete api("/projects/#{project.id}/repository/branches/foobar", user) expect(response).to have_http_status(404) -- cgit v1.2.1 From 593c912151eaef865d31fc2a8307ef6d337c2349 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 5 Dec 2016 15:40:53 +0100 Subject: Grapify the service API --- app/models/project_services/buildkite_service.rb | 3 +- app/models/project_services/drone_ci_service.rb | 3 +- .../project_services/emails_on_push_service.rb | 18 +- app/models/project_services/hipchat_service.rb | 6 +- app/models/project_services/irker_service.rb | 3 +- doc/api/services.md | 102 +++- lib/api/helpers.rb | 11 - lib/api/services.rb | 626 +++++++++++++++++++-- .../project_services/hipchat_service_spec.rb | 2 +- spec/requests/api/services_spec.rb | 5 +- spec/support/services_shared_context.rb | 6 + 11 files changed, 700 insertions(+), 85 deletions(-) diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 86a06321e21..fe6d7aabb22 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -3,7 +3,8 @@ require "addressable/uri" class BuildkiteService < CiService ENDPOINT = "https://buildkite.com" - prop_accessor :project_url, :token, :enable_ssl_verification + prop_accessor :project_url, :token + boolean_accessor :enable_ssl_verification validates :project_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 5e4dd101c53..adc78a427ee 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -1,5 +1,6 @@ class DroneCiService < CiService - prop_accessor :drone_url, :token, :enable_ssl_verification + prop_accessor :drone_url, :token + boolean_accessor :enable_ssl_verification validates :drone_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index e0083c43adb..79285cbd26d 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -1,6 +1,6 @@ class EmailsOnPushService < Service - prop_accessor :send_from_committer_email - prop_accessor :disable_diffs + boolean_accessor :send_from_committer_email + boolean_accessor :disable_diffs prop_accessor :recipients validates :recipients, presence: true, if: :activated? @@ -24,20 +24,20 @@ class EmailsOnPushService < Service return unless supported_events.include?(push_data[:object_kind]) EmailsOnPushWorker.perform_async( - project_id, - recipients, - push_data, - send_from_committer_email: send_from_committer_email?, - disable_diffs: disable_diffs? + project_id, + recipients, + push_data, + send_from_committer_email: send_from_committer_email?, + disable_diffs: disable_diffs? ) end def send_from_committer_email? - self.send_from_committer_email == "1" + Gitlab::Utils.to_boolean(self.send_from_committer_email) end def disable_diffs? - self.disable_diffs == "1" + Gitlab::Utils.to_boolean(self.disable_diffs) end def fields diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 660a8ae3421..915f6fed74c 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -8,8 +8,8 @@ class HipchatService < Service ul ol li dl dt dd ] - prop_accessor :token, :room, :server, :notify, :color, :api_version - boolean_accessor :notify_only_broken_builds + prop_accessor :token, :room, :server, :color, :api_version + boolean_accessor :notify_only_broken_builds, :notify validates :token, presence: true, if: :activated? def initialize_properties @@ -75,7 +75,7 @@ class HipchatService < Service end def message_options(data = nil) - { notify: notify.present? && notify == '1', color: message_color(data) } + { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) } end def create_message(data) diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index ce7d1c5d5b1..7355918feab 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -2,7 +2,8 @@ require 'uri' class IrkerService < Service prop_accessor :server_host, :server_port, :default_irc_uri - prop_accessor :colorize_messages, :recipients, :channels + prop_accessor :recipients, :channels + boolean_accessor :colorize_messages validates :recipients, presence: true, if: :activated? before_validation :get_channels diff --git a/doc/api/services.md b/doc/api/services.md index a5d733fe6c7..acb54448664 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -139,6 +139,40 @@ Get Buildkite service settings for a project. GET /projects/:id/services/buildkite ``` +## Build-Emails + +Get emails for GitLab CI builds. + +### Create/Edit Build-Emails service + +Set Build-Emails service for a project. + +``` +PUT /projects/:id/services/builds-email +``` + +Parameters: + +- `recipients` (**required**) - Comma-separated list of recipient email addresses +- `add_pusher` (optional) - Add pusher to recipients list +- `notify_only_broken_builds` (optional) -Notify only broken builds + +### Delete Build-Emails service + +Delete Build-Emails service for a project. + +``` +DELETE /projects/:id/services/builds-email +``` + +### Get Build-Emails service settings + +Get Build-Emails service settings for a project. + +``` +GET /projects/:id/services/builds-email +``` + ## Campfire Simple web-based real-time group chat @@ -476,12 +510,11 @@ PUT /projects/:id/services/jira | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `active` | boolean| no | Enable/disable the JIRA service. | | `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project, e.g., `https://jira.example.com`. | | `project_key` | string | yes | The short identifier for your JIRA project, all uppercase, e.g., `PROJ`. | | `username` | string | no | The username of the user created to be used with GitLab/JIRA. | | `password` | string | no | The password of the user created to be used with GitLab/JIRA. | -| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | +| `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | ### Delete JIRA service @@ -491,6 +524,71 @@ Remove all previously JIRA settings from a project. DELETE /projects/:id/services/jira ``` +## Mattermost Slash Commands + +Ability to receive slash commands from a Mattermost chat instance. + +### Create/Edit Mattermost Slash Command service + +Set Mattermost Slash Command for a project. + +``` +PUT /projects/:id/services/mattermost-slash-commands +``` + +Parameters: + +- `token` (**required**) - The Mattermost token + +### Delete Mattermost Slash Command service + +Delete Mattermost Slash Command service for a project. + +``` +DELETE /projects/:id/services/mattermost-slash-commands +``` + +### Get Mattermost Slash Command service settings + +Get Mattermost Slash Command service settings for a project. + +``` +GET /projects/:id/services/mattermost-slash-commands +``` + +## Pipeline-Emails + +Get emails for GitLab CI pipelines. + +### Create/Edit Pipeline-Emails service + +Set Pipeline-Emails service for a project. + +``` +PUT /projects/:id/services/pipelines-email +``` + +Parameters: + +- `recipients` (**required**) - Comma-separated list of recipient email addresses +- `notify_only_broken_builds` (optional) -Notify only broken pipelines + +### Delete Pipeline-Emails service + +Delete Pipeline-Emails service for a project. + +``` +DELETE /projects/:id/services/pipelines-email +``` + +### Get Pipeline-Emails service settings + +Get Pipeline-Emails service settings for a project. + +``` +GET /projects/:id/services/pipelines-email +``` + ## PivotalTracker Project Management Software (Source Commits Endpoint) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7f94ede7940..16ac40b7142 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -93,17 +93,6 @@ module API end end - def project_service(project = user_project) - @project_service ||= project.find_or_initialize_service(params[:service_slug].underscore) - @project_service || not_found!("Service") - end - - def service_attributes - @service_attributes ||= project_service.fields.inject([]) do |arr, hash| - arr << hash[:name].to_sym - end - end - def find_group(id) if id =~ /^\d+$/ Group.find_by(id: id) diff --git a/lib/api/services.rb b/lib/api/services.rb index bc427705777..fde2e2746f1 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -1,84 +1,602 @@ module API - # Projects API class Services < Grape::API + services = { + 'asana' => [ + { + required: true, + name: :api_key, + type: String, + desc: 'User API token' + }, + { + required: false, + name: :restrict_to_branch, + type: String, + desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches' + } + ], + 'assembla' => [ + { + required: true, + name: :token, + type: String, + desc: 'The authentication token' + }, + { + required: false, + name: :subdomain, + type: String, + desc: 'Subdomain setting' + } + ], + 'bamboo' => [ + { + required: true, + name: :bamboo_url, + type: String, + desc: 'Bamboo root URL like https://bamboo.example.com' + }, + { + required: true, + name: :build_key, + type: String, + desc: 'Bamboo build plan key like' + }, + { + required: true, + name: :username, + type: String, + desc: 'A user with API access, if applicable' + }, + { + required: true, + name: :password, + type: String, + desc: 'Passord of the user' + } + ], + 'bugzilla' => [ + { + required: true, + name: :new_issue_url, + type: String, + desc: 'New issue URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'Issues URL' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'Project URL' + }, + { + required: false, + name: :description, + type: String, + desc: 'Description' + }, + { + required: false, + name: :title, + type: String, + desc: 'Title' + } + ], + 'buildkite' => [ + { + required: true, + name: :token, + type: String, + desc: 'Buildkite project GitLab token' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'The buildkite project URL' + }, + { + required: false, + name: :enable_ssl_verification, + type: Boolean, + desc: 'Enable SSL verification for communication' + } + ], + 'builds-email' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Comma-separated list of recipient email addresses' + }, + { + required: false, + name: :add_pusher, + type: Boolean, + desc: 'Add pusher to recipients list' + }, + { + required: false, + name: :notify_only_broken_builds, + type: Boolean, + desc: 'Notify only broken builds' + } + ], + 'campfire' => [ + { + required: true, + name: :token, + type: String, + desc: 'Campfire token' + }, + { + required: false, + name: :subdomain, + type: String, + desc: 'Campfire subdomain' + }, + { + required: false, + name: :room, + type: String, + desc: 'Campfire room' + }, + ], + 'custom-issue-tracker' => [ + { + required: true, + name: :new_issue_url, + type: String, + desc: 'New issue URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'Issues URL' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'Project URL' + }, + { + required: false, + name: :description, + type: String, + desc: 'Description' + }, + { + required: false, + name: :title, + type: String, + desc: 'Title' + } + ], + 'drone-ci' => [ + { + required: true, + name: :token, + type: String, + desc: 'Drone CI token' + }, + { + required: true, + name: :drone_url, + type: String, + desc: 'Drone CI URL' + }, + { + required: false, + name: :enable_ssl_verification, + type: Boolean, + desc: 'Enable SSL verification for communication' + } + ], + 'emails-on-push' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Comma-separated list of recipient email addresses' + }, + { + required: false, + name: :disable_diffs, + type: Boolean, + desc: 'Disable code diffs' + }, + { + required: false, + name: :send_from_committer_email, + type: Boolean, + desc: 'Send from committer' + } + ], + 'external-wiki' => [ + { + required: true, + name: :external_wiki_url, + type: String, + desc: 'The URL of the external Wiki' + } + ], + 'flowdock' => [ + { + required: true, + name: :token, + type: String, + desc: 'Flowdock token' + } + ], + 'gemnasium' => [ + { + required: true, + name: :api_key, + type: String, + desc: 'Your personal API key on gemnasium.com' + }, + { + required: true, + name: :token, + type: String, + desc: "The project's slug on gemnasium.com" + } + ], + 'hipchat' => [ + { + required: true, + name: :token, + type: String, + desc: 'The room token' + }, + { + required: false, + name: :room, + type: String, + desc: 'The room name or ID' + }, + { + required: false, + name: :color, + type: String, + desc: 'The room color' + }, + { + required: false, + name: :notify, + type: Boolean, + desc: 'Enable notifications' + }, + { + required: false, + name: :api_version, + type: String, + desc: 'Leave blank for default (v2)' + }, + { + required: false, + name: :server, + type: String, + desc: 'Leave blank for default. https://hipchat.example.com' + } + ], + 'irker' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Recipients/channels separated by whitespaces' + }, + { + required: false, + name: :default_irc_uri, + type: String, + desc: 'Default: irc://irc.network.net:6697' + }, + { + required: false, + name: :server_host, + type: String, + desc: 'Server host. Default localhost' + }, + { + required: false, + name: :server_port, + type: Integer, + desc: 'Server port. Default 6659' + }, + { + required: false, + name: :colorize_messages, + type: Boolean, + desc: 'Colorize messages' + } + ], + 'jira' => [ + { + required: true, + name: :url, + type: String, + desc: 'The URL to the JIRA project which is being linked to this GitLab project, e.g., https://jira.example.com' + }, + { + required: true, + name: :project_key, + type: String, + desc: 'The short identifier for your JIRA project, all uppercase, e.g., PROJ' + }, + { + required: false, + name: :username, + type: String, + desc: 'The username of the user created to be used with GitLab/JIRA' + }, + { + required: false, + name: :password, + type: String, + desc: 'The password of the user created to be used with GitLab/JIRA' + }, + { + required: false, + name: :jira_issue_transition_id, + type: Integer, + desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`' + } + ], + 'mattermost-slash-commands' => [ + { + required: true, + name: :token, + type: String, + desc: 'The Mattermost token' + } + ], + 'pipelines-email' => [ + { + required: true, + name: :recipients, + type: String, + desc: 'Comma-separated list of recipient email addresses' + }, + { + required: false, + name: :notify_only_broken_builds, + type: Boolean, + desc: 'Notify only broken builds' + } + ], + 'pivotaltracker' => [ + { + required: true, + name: :token, + type: String, + desc: 'The Pivotaltracker token' + }, + { + required: false, + name: :restrict_to_branch, + type: String, + desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.' + } + ], + 'pushover' => [ + { + required: true, + name: :api_key, + type: String, + desc: 'The application key' + }, + { + required: true, + name: :user_key, + type: String, + desc: 'The user key' + }, + { + required: true, + name: :priority, + type: String, + desc: 'The priority' + }, + { + required: true, + name: :device, + type: String, + desc: 'Leave blank for all active devices' + }, + { + required: true, + name: :sound, + type: String, + desc: 'The sound of the notification' + } + ], + 'redmine' => [ + { + required: true, + name: :new_issue_url, + type: String, + desc: 'The new issue URL' + }, + { + required: true, + name: :project_url, + type: String, + desc: 'The project URL' + }, + { + required: true, + name: :issues_url, + type: String, + desc: 'The issues URL' + }, + { + required: false, + name: :description, + type: String, + desc: 'The description of the tracker' + } + ], + 'slack' => [ + { + required: true, + name: :webhook, + type: String, + desc: 'The Slack webhook. e.g. https://hooks.slack.com/services/...' + }, + { + required: false, + name: :new_issue_url, + type: String, + desc: 'The user name' + }, + { + required: false, + name: :channel, + type: String, + desc: 'The channel name' + } + ], + 'teamcity' => [ + { + required: true, + name: :teamcity_url, + type: String, + desc: 'TeamCity root URL like https://teamcity.example.com' + }, + { + required: true, + name: :build_type, + type: String, + desc: 'Build configuration ID' + }, + { + required: true, + name: :username, + type: String, + desc: 'A user with permissions to trigger a manual build' + }, + { + required: true, + name: :password, + type: String, + desc: 'The password of the user' + } + ] + }.freeze + + trigger_services = { + 'mattermost-slash-commands' => [ + { + name: :token, + type: String, + desc: 'The Mattermost token' + } + ] + }.freeze + resource :projects do before { authenticate! } before { authorize_admin_project } - # Set service for project - # - # Example Request: - # - # PUT /projects/:id/services/gitlab-ci - # - put ':id/services/:service_slug' do - if project_service - validators = project_service.class.validators.select do |s| - s.class == ActiveRecord::Validations::PresenceValidator && - s.attributes != [:project_id] + helpers do + def service_attributes(service) + service.fields.inject([]) do |arr, hash| + arr << hash[:name].to_sym end + end + end - required_attributes! validators.map(&:attributes).flatten.uniq - attrs = attributes_for_keys service_attributes + services.each do |service_slug, settings| + desc "Set #{service_slug} service for project" + params do + settings.each do |setting| + if setting[:required] + requires setting[:name], type: setting[:type], desc: setting[:desc] + else + optional setting[:name], type: setting[:type], desc: setting[:desc] + end + end + end + put ":id/services/#{service_slug}" do + service = user_project.find_or_initialize_service(service_slug.underscore) + service_params = declared_params(include_missing: false).merge(active: true) - if project_service.update_attributes(attrs.merge(active: true)) + if service.update_attributes(service_params) true else - not_found! + render_api_error!('400 Bad Request', 400) end end end - # Delete service for project - # - # Example Request: - # - # DELETE /project/:id/services/gitlab-ci - # - delete ':id/services/:service_slug' do - if project_service - attrs = service_attributes.inject({}) do |hash, key| - hash.merge!(key => nil) - end + desc "Delete a service for project" + params do + requires :service_slug, type: String, values: services.keys, desc: 'The name of the service' + end + delete ":id/services/:service_slug" do + service = user_project.find_or_initialize_service(params[:service_slug].underscore) - if project_service.update_attributes(attrs.merge(active: false)) - true - else - not_found! - end + attrs = service_attributes(service).inject({}) do |hash, key| + hash.merge!(key => nil) + end + + if service.update_attributes(attrs.merge(active: false)) + true + else + render_api_error!('400 Bad Request', 400) end end - # Get service settings for project - # - # Example Request: - # - # GET /project/:id/services/gitlab-ci - # - get ':id/services/:service_slug' do - present project_service, with: Entities::ProjectService, include_passwords: current_user.is_admin? + desc 'Get the service settings for project' do + success Entities::ProjectService + end + params do + requires :service_slug, type: String, values: services.keys, desc: 'The name of the service' + end + get ":id/services/:service_slug" do + service = user_project.find_or_initialize_service(params[:service_slug].underscore) + present service, with: Entities::ProjectService, include_passwords: current_user.is_admin? end end - resource :projects do - desc 'Trigger a slash command' do - detail 'Added in GitLab 8.13' + trigger_services.each do |service_slug, settings| + params do + requires :id, type: String, desc: 'The ID of a project' end - post ':id/services/:service_slug/trigger' do - project = find_project(params[:id]) + resource :projects do + desc "Trigger a slash command for #{service_slug}" do + detail 'Added in GitLab 8.13' + end + params do + settings.each do |setting| + requires setting[:name], type: setting[:type], desc: setting[:desc] + end + end + post ":id/services/#{service_slug.underscore}/trigger" do + project = find_project(params[:id]) - # This is not accurate, but done to prevent leakage of the project names - not_found!('Service') unless project + # This is not accurate, but done to prevent leakage of the project names + not_found!('Service') unless project - service = project_service(project) + service = project.find_or_initialize_service(service_slug.underscore) - result = service.try(:active?) && service.try(:trigger, params) + result = service.try(:active?) && service.try(:trigger, params) - if result - status result[:status] || 200 - present result - else - not_found!('Service') + if result + status result[:status] || 200 + present result + else + not_found!('Service') + end end end end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 2da3a9cb09f..564e49d5459 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -358,7 +358,7 @@ describe HipchatService, models: true do context 'with a failed build' do it 'uses the red color' do build_data = { object_kind: 'build', commit: { status: 'failed' } } - + expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' }) end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index d30361f53d4..668e39f9dba 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -2,6 +2,7 @@ require "spec_helper" describe API::Services, api: true do include ApiHelpers + let(:user) { create(:user) } let(:admin) { create(:admin) } let(:user2) { create(:user) } @@ -98,7 +99,7 @@ describe API::Services, api: true do post api("/projects/#{project.id}/services/idonotexist/trigger") expect(response).to have_http_status(404) - expect(json_response["message"]).to eq("404 Service Not Found") + expect(json_response["error"]).to eq("404 Not Found") end end @@ -114,7 +115,7 @@ describe API::Services, api: true do end it 'when the service is inactive' do - post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger") + post api("/projects/#{project.id}/services/mattermost_slash_commands/trigger"), params expect(response).to have_http_status(404) end diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb index d1c999cad4d..66c93890e31 100644 --- a/spec/support/services_shared_context.rb +++ b/spec/support/services_shared_context.rb @@ -16,8 +16,14 @@ Service.available_services_names.each do |service| hash.merge!(k => 'secrettoken') elsif k =~ /^(.*_url|url|webhook)/ hash.merge!(k => "http://example.com") + elsif service_klass.method_defined?("#{k}?") + hash.merge!(k => true) elsif service == 'irker' && k == :recipients hash.merge!(k => 'irc://irc.network.net:666/#channel') + elsif service == 'irker' && k == :server_port + hash.merge!(k => 1234) + elsif service == 'jira' && k == :jira_issue_transition_id + hash.merge!(k => 1234) else hash.merge!(k => "someword") end -- cgit v1.2.1 From 69ffa81424e16a337b4c39d2cb9c0dff8b92f71b Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 23:30:54 -0600 Subject: fix alignment for notification settings ajax response --- app/controllers/notification_settings_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index 8ec4bb1233f..160259626d6 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -37,7 +37,11 @@ class NotificationSettingsController < ApplicationController def render_response render json: { - html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting), + html: view_to_html_string( + "shared/notifications/_button", + notification_setting: @notification_setting, + left_align: @notification_setting.source.nil? + ), saved: @saved } end -- cgit v1.2.1 From 097c283ac186bccb647143fd022bfe6a320379b0 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 10 Dec 2016 00:21:47 -0600 Subject: remove left_align setting from notification setting dropdown in favor of a pure css solution --- app/assets/stylesheets/pages/notifications.scss | 4 ++++ app/assets/stylesheets/pages/projects.scss | 4 ++++ app/controllers/notification_settings_controller.rb | 6 +----- app/views/profiles/notifications/show.html.haml | 2 +- app/views/shared/notifications/_button.html.haml | 3 +-- app/views/shared/notifications/_notification_dropdown.html.haml | 3 +-- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss index 94fbbef3c77..7d61390a439 100644 --- a/app/assets/stylesheets/pages/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -1,5 +1,9 @@ .notification-list-item { line-height: 34px; + + .dropdown-menu { + @extend .dropdown-menu-align-right; + } } .notification { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 72b6685d940..6e0f6b1cd81 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -188,6 +188,10 @@ margin-left: 10px; } + .notification-dropdown .dropdown-menu { + @extend .dropdown-menu-align-right; + } + .download-button { @media (max-width: $screen-md-max) { margin-left: 0; diff --git a/app/controllers/notification_settings_controller.rb b/app/controllers/notification_settings_controller.rb index 160259626d6..8ec4bb1233f 100644 --- a/app/controllers/notification_settings_controller.rb +++ b/app/controllers/notification_settings_controller.rb @@ -37,11 +37,7 @@ class NotificationSettingsController < ApplicationController def render_response render json: { - html: view_to_html_string( - "shared/notifications/_button", - notification_setting: @notification_setting, - left_align: @notification_setting.source.nil? - ), + html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting), saved: @saved } end diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 844fce59704..d79a1a9f368 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -30,7 +30,7 @@ %br .clearfix .form-group.pull-left.global-notification-setting - = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true + = render 'shared/notifications/button', notification_setting: @global_notification_setting .clearfix diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 1f7df0bcd19..fbad0d05de3 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,4 +1,3 @@ -- left_align = local_assigns[:left_align] - if notification_setting .dropdown.notification-dropdown = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| @@ -19,7 +18,7 @@ = notification_title(notification_setting.level) = icon("caret-down") - = render "shared/notifications/notification_dropdown", notification_setting: notification_setting, left_align: left_align + = render "shared/notifications/notification_dropdown", notification_setting: notification_setting = content_for :scripts_body do = render "shared/notifications/custom_notifications", notification_setting: notification_setting diff --git a/app/views/shared/notifications/_notification_dropdown.html.haml b/app/views/shared/notifications/_notification_dropdown.html.haml index d3258ee64cb..85ad74f9a39 100644 --- a/app/views/shared/notifications/_notification_dropdown.html.haml +++ b/app/views/shared/notifications/_notification_dropdown.html.haml @@ -1,5 +1,4 @@ -- left_align = local_assigns[:left_align] -%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting), ("dropdown-menu-align-right" unless left_align)] } +%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting)] } - NotificationSetting.levels.each_key do |level| - next if level == "custom" - next if level == "global" && notification_setting.source.nil? -- cgit v1.2.1 From 5934698fc3b7aaf6623959a9740e62ed3d341a42 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 3 Oct 2016 16:14:17 -0500 Subject: fix broken string interpolation --- app/views/projects/commit/_change.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index f6e3d5e76f5..59af45ee1ed 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -13,7 +13,7 @@ %a.close{href: "#", "data-dismiss" => "modal"} × %h3.page-title== #{label} this #{commit.change_type_title(current_user)} .modal-body - = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do + = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do .form-group.branch = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 -- cgit v1.2.1 From 7876e83d0c91e75b91e21ddb9993fc39fde96731 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 3 Oct 2016 17:04:23 -0500 Subject: remove unnecessary nonce id --- app/views/projects/commit/_change.html.haml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index 59af45ee1ed..e38ee6a2afa 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -23,9 +23,8 @@ - if can?(current_user, :push_code, @project) .js-create-merge-request-container .checkbox - - nonce = SecureRandom.hex - = label_tag "create_merge_request-#{nonce}" do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" + = label_tag do + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request' Start a new merge request with these changes - else = hidden_field_tag 'create_merge_request', 1 -- cgit v1.2.1 From 79aad815272fdebca30080cb33dd3fa77ef72350 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 5 Oct 2016 10:12:46 -0500 Subject: fix awkward verb conjugation in cherry-pick and revert errors --- app/services/commits/change_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index db5f2bf9b2e..4d410f66c55 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -35,7 +35,7 @@ module Commits success else error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. - It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content." + A #{action.to_s.dasherize} may have already been performed with this #{@commit.change_type_title(current_user)}, or a more recent commit may have updated some of its content." raise ChangeError, error_msg end end -- cgit v1.2.1 From 130cbc979aff4ce746cabc7d319a34904b1fd611 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Sat, 10 Dec 2016 00:44:04 -0600 Subject: prevent create_merge_request form field helpers from generating an id value --- app/views/projects/commit/_change.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index e38ee6a2afa..782f558e8b0 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -24,10 +24,10 @@ .js-create-merge-request-container .checkbox = label_tag do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request' + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil Start a new merge request with these changes - else - = hidden_field_tag 'create_merge_request', 1 + = hidden_field_tag 'create_merge_request', 1, id: nil .form-actions = submit_tag label, class: 'btn btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" -- cgit v1.2.1 From 758b3055c5b25e2b5dcdb12c169d23303d3bfe0d Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 14:58:27 -0600 Subject: update action button order for snippets page --- app/views/projects/snippets/_actions.html.haml | 12 ++++++------ app/views/snippets/_actions.html.haml | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index 32e1f8a21b0..346badb8f25 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -1,13 +1,13 @@ .hidden-xs - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do - New snippet - - if can?(current_user, :update_project_snippet, @snippet) - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do - Delete - if can?(current_user, :update_project_snippet, @snippet) = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do Edit + - if can?(current_user, :update_project_snippet, @snippet) + = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do + Delete + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do + New snippet - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 1d0e549ed3d..2ed5894d312 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,13 +1,13 @@ .hidden-xs - - if current_user - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do - New snippet - - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do - Delete - if can?(current_user, :update_personal_snippet, @snippet) = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do Edit + - if can?(current_user, :admin_personal_snippet, @snippet) + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do + Delete + - if current_user + = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do + New snippet - if current_user .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } -- cgit v1.2.1 From 12a8095cc7a30469c4546af1ed801c785b0ebace Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 15:08:19 -0600 Subject: remove unused class name --- app/views/projects/snippets/_actions.html.haml | 4 ++-- app/views/snippets/_actions.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index 346badb8f25..d2a97476957 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -1,12 +1,12 @@ .hidden-xs - if can?(current_user, :update_project_snippet, @snippet) - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do Edit - if can?(current_user, :update_project_snippet, @snippet) = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create', title: "New snippet" do New snippet - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 2ed5894d312..ebb3dfe5a34 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,12 +1,12 @@ .hidden-xs - if can?(current_user, :update_personal_snippet, @snippet) - = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do + = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do Edit - if can?(current_user, :admin_personal_snippet, @snippet) = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do Delete - if current_user - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do + = link_to new_snippet_path, class: "btn btn-grouped btn-create", title: "New snippet" do New snippet - if current_user .visible-xs-block.dropdown -- cgit v1.2.1 From c41b7e8a2f4df9a3a320cbf1b5b60a43709ca9b6 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 15:22:16 -0600 Subject: invert snippet action buttons --- app/views/projects/snippets/_actions.html.haml | 4 ++-- app/views/snippets/_actions.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index d2a97476957..068a6610350 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -3,10 +3,10 @@ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do Edit - if can?(current_user, :update_project_snippet, @snippet) - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do + = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do Delete - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create', title: "New snippet" do + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do New snippet - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index ebb3dfe5a34..95fc7198104 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -3,10 +3,10 @@ = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do Edit - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do Delete - if current_user - = link_to new_snippet_path, class: "btn btn-grouped btn-create", title: "New snippet" do + = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do New snippet - if current_user .visible-xs-block.dropdown -- cgit v1.2.1 From 6b20ad3646694ae90e8375f92cb5df13e2fd9fad Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 15:41:39 -0600 Subject: remove plus icon in "new snippet" button --- app/views/dashboard/snippets/index.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index b2af438ea57..62618625454 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -6,7 +6,6 @@ .nav-block .controls.hidden-xs = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do - = icon('plus') New snippet .nav-links.snippet-scope-menu @@ -36,7 +35,6 @@ .visible-xs = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do - = icon('plus') New snippet = render 'snippets/snippets' -- cgit v1.2.1 From b65d3e1132762b9e1a39ce908b3b481f92d9a10c Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 16:03:00 -0600 Subject: move new snippet button to main snippet navigation block --- app/views/dashboard/_snippets_head.html.haml | 20 +++++---- app/views/dashboard/snippets/index.html.haml | 62 +++++++++++++--------------- app/views/explore/snippets/index.html.haml | 8 ---- 3 files changed, 42 insertions(+), 48 deletions(-) diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index b25e8ea1f0c..02e90bbfa55 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,7 +1,13 @@ -%ul.nav-links - = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do - = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do - Your Snippets - = nav_link(page: explore_snippets_path) do - = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do - Explore Snippets +.top-area + %ul.nav-links + = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do + = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do + Your Snippets + = nav_link(page: explore_snippets_path) do + = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do + Explore Snippets + + - if current_user + .nav-controls.hidden-xs + = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do + New snippet diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 62618625454..13cba7ca224 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -3,38 +3,34 @@ = render 'dashboard/snippets_head' -.nav-block - .controls.hidden-xs - = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do - New snippet - - .nav-links.snippet-scope-menu - %li{ class: ("active" unless params[:scope]) } - = link_to dashboard_snippets_path do - All - %span.badge - = current_user.snippets.count - - %li{ class: ("active" if params[:scope] == "are_private") } - = link_to dashboard_snippets_path(scope: 'are_private') do - Private - %span.badge - = current_user.snippets.are_private.count - - %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to dashboard_snippets_path(scope: 'are_internal') do - Internal - %span.badge - = current_user.snippets.are_internal.count - - %li{ class: ("active" if params[:scope] == "are_public") } - = link_to dashboard_snippets_path(scope: 'are_public') do - Public - %span.badge - = current_user.snippets.are_public.count - - .visible-xs - = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do - New snippet +.nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to dashboard_snippets_path do + All + %span.badge + = current_user.snippets.count + + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to dashboard_snippets_path(scope: 'are_private') do + Private + %span.badge + = current_user.snippets.are_private.count + + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to dashboard_snippets_path(scope: 'are_internal') do + Internal + %span.badge + = current_user.snippets.are_internal.count + + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to dashboard_snippets_path(scope: 'are_public') do + Public + %span.badge + = current_user.snippets.are_public.count + +.visible-xs +   + = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do + New snippet = render 'snippets/snippets' diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 7def9eacdc9..9b5ea13ca29 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -6,12 +6,4 @@ - else = render 'explore/head' -.row-content-block - - if current_user - = link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do - New snippet - - .oneline - Public snippets created by you and other users are listed here - = render 'snippets/snippets' -- cgit v1.2.1 From 54a1193d790ae40fea5db1d8596c12fbc7a93576 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 16:35:53 -0600 Subject: add scope filters to project snippets page --- app/controllers/projects/snippets_controller.rb | 8 +++++--- app/finders/snippets_finder.rb | 24 ++++++++++++++++++---- app/views/projects/snippets/index.html.haml | 27 +++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index e290a0eadda..0720be2e55d 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -19,10 +19,12 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = SnippetsFinder.new.execute(current_user, { + @snippets = SnippetsFinder.new.execute( + current_user, filter: :by_project, - project: @project - }) + project: @project, + scope: params[:scope] + ) @snippets = @snippets.page(params[:page]) end diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 00ff1611039..99f1e73c800 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -8,7 +8,7 @@ class SnippetsFinder when :by_user then by_user(current_user, params[:user], params[:scope]) when :by_project - by_project(current_user, params[:project]) + by_project(current_user, params[:project], params[:scope]) end end @@ -47,14 +47,30 @@ class SnippetsFinder end end - def by_project(current_user, project) + def by_project(current_user, project, scope) snippets = project.snippets.fresh if current_user if project.team.member?(current_user) || current_user.admin? - snippets + case scope + when 'are_internal' then + snippets.are_internal + when 'are_private' then + snippets.are_private + when 'are_public' then + snippets.are_public + else + snippets + end else - snippets.public_and_internal + case scope + when 'are_internal' then + snippets.are_internal + when 'are_public' then + snippets.are_public + else + snippets.public_and_internal + end end else snippets.are_public diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index e77e1b026f6..76792fb5326 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,5 +1,32 @@ - page_title "Snippets" +- if current_user + .nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to namespace_project_snippets_path(@project.namespace, @project) do + All + %span.badge + = @project.snippets.count + + - if @project.team.member?(current_user) || current_user.admin? + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_private') do + Private + %span.badge + = @project.snippets.are_private.count + + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_internal') do + Internal + %span.badge + = @project.snippets.are_internal.count + + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_public') do + Public + %span.badge + = @project.snippets.are_public.count + .sub-header-block - if can?(current_user, :create_project_snippet, @project) = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do -- cgit v1.2.1 From 68bb459b160419004ef2110b2824c8b2ab4c9739 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 16:48:26 -0600 Subject: move project new snippet button into snippet scope navigation header --- app/views/projects/snippets/index.html.haml | 58 +++++++++++++++-------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 76792fb5326..35c4e9d85ad 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,38 +1,42 @@ - page_title "Snippets" - if current_user - .nav-links.snippet-scope-menu - %li{ class: ("active" unless params[:scope]) } - = link_to namespace_project_snippets_path(@project.namespace, @project) do - All - %span.badge - = @project.snippets.count + .top-area + .nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to namespace_project_snippets_path(@project.namespace, @project) do + All + %span.badge + = @project.snippets.count + + - if @project.team.member?(current_user) || current_user.admin? + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_private') do + Private + %span.badge + = @project.snippets.are_private.count - - if @project.team.member?(current_user) || current_user.admin? - %li{ class: ("active" if params[:scope] == "are_private") } - = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_private') do - Private + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_internal') do + Internal %span.badge - = @project.snippets.are_private.count + = @project.snippets.are_internal.count - %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_internal') do - Internal - %span.badge - = @project.snippets.are_internal.count + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_public') do + Public + %span.badge + = @project.snippets.are_public.count - %li{ class: ("active" if params[:scope] == "are_public") } - = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_public') do - Public - %span.badge - = @project.snippets.are_public.count + .nav-controls.hidden-xs + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do + New snippet -.sub-header-block - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do +- if can?(current_user, :create_project_snippet, @project) + .visible-xs +   + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do New snippet - .oneline - Share code pastes with others out of git repository - = render 'snippets/snippets' -- cgit v1.2.1 From 1ea478476404d6696d65269fd2ffe6ca29740035 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Wed, 7 Dec 2016 16:49:06 -0600 Subject: ensure all snippets count badge is accurate for non team members --- app/views/projects/snippets/index.html.haml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 35c4e9d85ad..978f4b87564 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -7,7 +7,10 @@ = link_to namespace_project_snippets_path(@project.namespace, @project) do All %span.badge - = @project.snippets.count + - if @project.team.member?(current_user) || current_user.admin? + = @project.snippets.count + - else + = @project.snippets.public_and_internal.count - if @project.team.member?(current_user) || current_user.admin? %li{ class: ("active" if params[:scope] == "are_private") } -- cgit v1.2.1 From 68c1e3a1568d9f61bf1e01d6ff55ce59c5c3eaaf Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 8 Dec 2016 14:29:15 -0600 Subject: update snippets list design --- app/assets/stylesheets/pages/snippets.scss | 10 ++++++++++ app/views/shared/snippets/_snippet.html.haml | 23 ++++++++++------------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 857eb76131a..e6e86556695 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -1,3 +1,13 @@ +.snippet-row { + .title { + margin-bottom: 2px; + } + + .snippet-filename { + padding: 0 2px; + } +} + .snippet-form-holder .file-holder .file-title { padding: 2px; } diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index ea17bec8677..95985ad6afb 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -4,14 +4,11 @@ .title = link_to reliable_snippet_path(snippet) do = snippet.title - - if snippet.private? - %span.label.label-gray.hidden-xs - = icon('lock') - private - %span.monospace.pull-right.hidden-xs - = snippet.file_name + - if snippet.file_name + %span.snippet-filename.monospace.hidden-xs + = snippet.file_name - %ul.controls.visible-xs + %ul.controls %li - note_count = snippet.notes.user.count = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do @@ -22,11 +19,11 @@ = visibility_level_label(snippet.visibility_level) = visibility_level_icon(snippet.visibility_level, fw: false) - %small.pull-right.cgray.hidden-xs - - if snippet.project_id? - = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) - - .snippet-info.hidden-xs + .snippet-info + #{snippet.to_reference} · + authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom')} by = link_to user_snippets_path(snippet.author) do = snippet.author_name - authored #{time_ago_with_tooltip(snippet.created_at)} + + .pull-right.snippet-updated-at + %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom', html_class: 'snippet_update_ago')} -- cgit v1.2.1 From 7f3fc26ec98193fa0c3bfeb7b78c81176bb9c689 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 8 Dec 2016 16:13:23 -0600 Subject: fix failing tests --- app/views/shared/snippets/_snippet.html.haml | 4 ++-- features/steps/project/snippets.rb | 2 +- spec/features/dashboard/datetime_on_tooltips_spec.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 95985ad6afb..65946910529 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -21,9 +21,9 @@ .snippet-info #{snippet.to_reference} · - authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom')} by + authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')} by = link_to user_snippets_path(snippet.author) do = snippet.author_name .pull-right.snippet-updated-at - %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom', html_class: 'snippet_update_ago')} + %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')} diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb index 5e7d539add6..a3bebfa4b71 100644 --- a/features/steps/project/snippets.rb +++ b/features/steps/project/snippets.rb @@ -22,7 +22,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps end step 'I click link "New snippet"' do - click_link "New snippet" + first(:link, "New snippet").click end step 'I click link "Snippet one"' do diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 365cb445df1..44dfc2dff45 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -36,7 +36,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do visit user_snippets_path(user) wait_for_ajax() - page.find('.js-timeago').hover + page.find('.js-timeago.snippet-created-ago').hover end it 'has the datetime formated correctly' do -- cgit v1.2.1 From 0608ecbc69c991efcf56f7872cf1b06416d4bd10 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 10:54:13 -0600 Subject: conditionally display assoc project info in snippets index --- app/views/dashboard/snippets/index.html.haml | 2 +- app/views/explore/snippets/index.html.haml | 2 +- app/views/shared/snippets/_snippet.html.haml | 10 +++++++++- app/views/snippets/_snippets.html.haml | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 13cba7ca224..81bfa44a665 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -33,4 +33,4 @@ = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do New snippet -= render 'snippets/snippets' += render partial: 'snippets/snippets', locals: { link_project: true } diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 9b5ea13ca29..e5706d04736 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -6,4 +6,4 @@ - else = render 'explore/head' -= render 'snippets/snippets' += render partial: 'snippets/snippets', locals: { link_project: true } diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 65946910529..5d2d2317f22 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -1,3 +1,5 @@ +- link_project = local_assigns.fetch(:link_project, false) + %li.snippet-row = image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: '' @@ -21,9 +23,15 @@ .snippet-info #{snippet.to_reference} · - authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')} by + authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')} + by = link_to user_snippets_path(snippet.author) do = snippet.author_name + - if link_project && snippet.project_id? + %span.hidden-xs + in + = link_to namespace_project_path(snippet.project.namespace, snippet.project) do + = snippet.project.name_with_namespace .pull-right.snippet-updated-at %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')} diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 77b66ca74b6..ac3701233ad 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,8 +1,9 @@ - remote = local_assigns.fetch(:remote, false) +- link_project = local_assigns.fetch(:link_project, false) .snippets-list-holder %ul.content-list - = render partial: 'shared/snippets/snippet', collection: @snippets + = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project } - if @snippets.empty? %li .nothing-here-block Nothing here. -- cgit v1.2.1 From 6dc4007ad6c775a8d82bb1e3e2e15ec6e8143b14 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 11:45:40 -0600 Subject: fix snippets reference id in search results (should be $ not #) --- app/views/search/results/_snippet_title.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index c414acb6a11..027d42396b4 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -14,7 +14,7 @@ = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project) .snippet-info - = "##{snippet_title.id}" + = snippet_title.to_reference %span by = link_to user_snippets_path(snippet_title.author) do -- cgit v1.2.1 From eaf92daa2ce2601426c991c794aab57c4d0da420 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 11:48:53 -0600 Subject: move snippet edited timeago under the snippet title --- app/assets/stylesheets/pages/snippets.scss | 10 ++++++++-- app/views/shared/snippets/_header.html.haml | 12 ++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index e6e86556695..ff13b86acf0 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -34,11 +34,17 @@ padding-bottom: $gl-padding; } +.snippet-header { + padding: $gl-padding 0; +} + .snippet-title { font-size: 24px; font-weight: 600; - padding: $gl-padding; - padding-left: 0; +} + +.snippet-edited-ago { + color: $gray-darkest; } .snippet-actions { diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index d7506e07ff6..d084f5e9684 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -8,10 +8,6 @@ %span.creator authored = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') - - if @snippet.updated_at != @snippet.created_at - %span - = icon('edit', title: 'edited') - = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago') by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")} .snippet-actions @@ -20,5 +16,9 @@ - else = render "snippets/actions" -%h2.snippet-title.prepend-top-0.append-bottom-0 - = markdown_field(@snippet, :title) +.snippet-header + %h2.snippet-title.prepend-top-0.append-bottom-0 + = markdown_field(@snippet, :title) + + - if @snippet.updated_at != @snippet.created_at + = edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago') -- cgit v1.2.1 From adbc37804e49e1d3ba02bf61122696e135666ff3 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 14:40:48 -0600 Subject: refactor duplicate code into a by_scope method --- app/finders/snippets_finder.rb | 54 +++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 99f1e73c800..31f039b5a70 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -29,21 +29,11 @@ class SnippetsFinder def by_user(current_user, user, scope) snippets = user.snippets.fresh - return snippets.are_public unless current_user - - if user == current_user - case scope - when 'are_internal' then - snippets.are_internal - when 'are_private' then - snippets.are_private - when 'are_public' then - snippets.are_public - else - snippets - end + if current_user + include_private = user == current_user + by_scope(snippets, scope, include_private) else - snippets.public_and_internal + snippets.are_public end end @@ -51,29 +41,23 @@ class SnippetsFinder snippets = project.snippets.fresh if current_user - if project.team.member?(current_user) || current_user.admin? - case scope - when 'are_internal' then - snippets.are_internal - when 'are_private' then - snippets.are_private - when 'are_public' then - snippets.are_public - else - snippets - end - else - case scope - when 'are_internal' then - snippets.are_internal - when 'are_public' then - snippets.are_public - else - snippets.public_and_internal - end - end + include_private = project.team.member?(current_user) || current_user.admin? + by_scope(snippets, scope, include_private) else snippets.are_public end end + + def by_scope(snippets, scope = nil, include_private = false) + case scope.to_s + when 'are_private' + include_private ? snippets.are_private : nil + when 'are_internal' + snippets.are_internal + when 'are_public' + snippets.are_public + else + include_private ? snippets : snippets.public_and_internal + end + end end -- cgit v1.2.1 From 687872978100c168ce381448c0a9536fb53542ce Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 15:38:10 -0600 Subject: implement snippets_scope_menu partial to reduce code duplication --- app/helpers/snippets_helper.rb | 11 ++++++++ app/views/dashboard/snippets/index.html.haml | 26 +------------------ app/views/projects/snippets/index.html.haml | 30 ++-------------------- app/views/snippets/_snippets_scope_menu.html.haml | 31 +++++++++++++++++++++++ 4 files changed, 45 insertions(+), 53 deletions(-) create mode 100644 app/views/snippets/_snippets_scope_menu.html.haml diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 7e33a562077..fc7febd3385 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -8,6 +8,17 @@ module SnippetsHelper end end + # Return the path of a snippets index for a user or for a project + # + # @returns String, path to snippet index + def snippets_path(subject = nil, opts = nil) + if subject.is_a?(Project) + namespace_project_snippets_path(subject.namespace, subject, opts) + else # assume subject === User + dashboard_snippets_path(opts) + end + end + # Get an array of line numbers surrounding a matching # line, bounded by min/max. # diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 81bfa44a665..85cbe0bf0e6 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -2,31 +2,7 @@ - header_title "Snippets", dashboard_snippets_path = render 'dashboard/snippets_head' - -.nav-links.snippet-scope-menu - %li{ class: ("active" unless params[:scope]) } - = link_to dashboard_snippets_path do - All - %span.badge - = current_user.snippets.count - - %li{ class: ("active" if params[:scope] == "are_private") } - = link_to dashboard_snippets_path(scope: 'are_private') do - Private - %span.badge - = current_user.snippets.are_private.count - - %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to dashboard_snippets_path(scope: 'are_internal') do - Internal - %span.badge - = current_user.snippets.are_internal.count - - %li{ class: ("active" if params[:scope] == "are_public") } - = link_to dashboard_snippets_path(scope: 'are_public') do - Public - %span.badge - = current_user.snippets.are_public.count += render partial: 'snippets/snippets_scope_menu', locals: { include_private: true } .visible-xs   diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 978f4b87564..84e05cd6d88 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -2,34 +2,8 @@ - if current_user .top-area - .nav-links.snippet-scope-menu - %li{ class: ("active" unless params[:scope]) } - = link_to namespace_project_snippets_path(@project.namespace, @project) do - All - %span.badge - - if @project.team.member?(current_user) || current_user.admin? - = @project.snippets.count - - else - = @project.snippets.public_and_internal.count - - - if @project.team.member?(current_user) || current_user.admin? - %li{ class: ("active" if params[:scope] == "are_private") } - = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_private') do - Private - %span.badge - = @project.snippets.are_private.count - - %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_internal') do - Internal - %span.badge - = @project.snippets.are_internal.count - - %li{ class: ("active" if params[:scope] == "are_public") } - = link_to namespace_project_snippets_path(@project.namespace, @project, scope: 'are_public') do - Public - %span.badge - = @project.snippets.are_public.count + - include_private = @project.team.member?(current_user) || current_user.admin? + = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } .nav-controls.hidden-xs - if can?(current_user, :create_project_snippet, @project) diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml new file mode 100644 index 00000000000..cb837f1fac1 --- /dev/null +++ b/app/views/snippets/_snippets_scope_menu.html.haml @@ -0,0 +1,31 @@ +- subject = local_assigns.fetch(:subject, current_user) +- include_private = local_assigns.fetch(:include_private, false) + +.nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to snippets_path(subject) do + All + %span.badge + - if include_private + = subject.snippets.count + - else + = subject.snippets.public_and_internal.count + + - if include_private + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to snippets_path(subject, scope: 'are_private') do + Private + %span.badge + = subject.snippets.are_private.count + + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to snippets_path(subject, scope: 'are_internal') do + Internal + %span.badge + = subject.snippets.are_internal.count + + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to snippets_path(subject, scope: 'are_public') do + Public + %span.badge + = subject.snippets.are_public.count -- cgit v1.2.1 From a68735d4985bf5ffaeaf5a051b40f8aed0c0a6e0 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 15:43:02 -0600 Subject: use Snippet.none in favor of nil to allow chaining --- app/finders/snippets_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 31f039b5a70..78a2f8840ed 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -51,7 +51,7 @@ class SnippetsFinder def by_scope(snippets, scope = nil, include_private = false) case scope.to_s when 'are_private' - include_private ? snippets.are_private : nil + include_private ? snippets.are_private : Snippet.none when 'are_internal' snippets.are_internal when 'are_public' -- cgit v1.2.1 From dccd53e1ce1903f2df8dd20023e2bafd96d850f3 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 16:01:08 -0600 Subject: add new tests for snippets_finder.rb --- spec/finders/snippets_finder_spec.rb | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 28bdc18e840..21ccdaa6c38 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -84,16 +84,39 @@ describe SnippetsFinder do expect(snippets).not_to include(@snippet1, @snippet2) end - it "returns public and internal snippets for none project members" do + it "returns public and internal snippets for non project members" do snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) expect(snippets).to include(@snippet2, @snippet3) expect(snippets).not_to include(@snippet1) end + it "returns public snippets for non project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_public") + expect(snippets).to include(@snippet3) + expect(snippets).not_to include(@snippet1, @snippet2) + end + + it "returns internal snippets for non project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_internal") + expect(snippets).to include(@snippet2) + expect(snippets).not_to include(@snippet1, @snippet3) + end + + it "does not return private snippets for non project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_private") + expect(snippets).not_to include(@snippet1, @snippet2, @snippet3) + end + it "returns all snippets for project members" do project1.team << [user, :developer] snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) expect(snippets).to include(@snippet1, @snippet2, @snippet3) end + + it "returns private snippets for project members" do + project1.team << [user, :developer] + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1, scope: "are_private") + expect(snippets).to include(@snippet1) + end end end -- cgit v1.2.1 From 730ff2e50b600f3e3c79e69fd4978faf6a06ce1b Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 17:33:23 -0600 Subject: rename snippets_path helper due to conflict --- app/helpers/snippets_helper.rb | 2 +- app/views/snippets/_snippets_scope_menu.html.haml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index fc7febd3385..8c02b4061ca 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -11,7 +11,7 @@ module SnippetsHelper # Return the path of a snippets index for a user or for a project # # @returns String, path to snippet index - def snippets_path(subject = nil, opts = nil) + def subject_snippets_path(subject = nil, opts = nil) if subject.is_a?(Project) namespace_project_snippets_path(subject.namespace, subject, opts) else # assume subject === User diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml index cb837f1fac1..2dda5fed647 100644 --- a/app/views/snippets/_snippets_scope_menu.html.haml +++ b/app/views/snippets/_snippets_scope_menu.html.haml @@ -3,7 +3,7 @@ .nav-links.snippet-scope-menu %li{ class: ("active" unless params[:scope]) } - = link_to snippets_path(subject) do + = link_to subject_snippets_path(subject) do All %span.badge - if include_private @@ -13,19 +13,19 @@ - if include_private %li{ class: ("active" if params[:scope] == "are_private") } - = link_to snippets_path(subject, scope: 'are_private') do + = link_to subject_snippets_path(subject, scope: 'are_private') do Private %span.badge = subject.snippets.are_private.count %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to snippets_path(subject, scope: 'are_internal') do + = link_to subject_snippets_path(subject, scope: 'are_internal') do Internal %span.badge = subject.snippets.are_internal.count %li{ class: ("active" if params[:scope] == "are_public") } - = link_to snippets_path(subject, scope: 'are_public') do + = link_to subject_snippets_path(subject, scope: 'are_public') do Public %span.badge = subject.snippets.are_public.count -- cgit v1.2.1 From 9355ba238b7ffde61888fb21a1ed1b33414a7d9a Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 16:36:05 -0600 Subject: normalize author email so we can treat it as case insensitive --- app/assets/javascripts/graphs/stat_graph_contributors_util.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index 051ff98c774..1982f4af939 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -2,7 +2,7 @@ (function() { window.ContributorsStatGraphUtil = { parse_log: function(log) { - var by_author, by_email, data, entry, i, len, total; + var by_author, by_email, data, entry, i, len, total, normalized_email; total = {}; by_author = {}; by_email = {}; @@ -11,7 +11,8 @@ if (total[entry.date] == null) { this.add_date(entry.date, total); } - data = by_author[entry.author_name] || by_email[entry.author_email]; + normalized_email = entry.author_email.toLowerCase(); + data = by_author[entry.author_name] || by_email[normalized_email]; if (data == null) { data = this.add_author(entry, by_author, by_email); } @@ -32,12 +33,14 @@ return collection[date].date = date; }, add_author: function(author, by_author, by_email) { - var data; + var data, normalized_email; data = {}; data.author_name = author.author_name; data.author_email = author.author_email; + normalized_email = author.author_email.toLowerCase(); by_author[author.author_name] = data; - return by_email[author.author_email] = data; + by_email[normalized_email] = data; + return data; }, store_data: function(entry, total, by_author) { this.store_commits(total, by_author); -- cgit v1.2.1 From 9e3b17faab915a0f422ed88f1014878856c188b7 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 9 Dec 2016 16:45:10 -0600 Subject: add CHANGELOG entry for !8021 --- changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml diff --git a/changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml b/changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml new file mode 100644 index 00000000000..742b10e72aa --- /dev/null +++ b/changelogs/unreleased/19550-fix-contributer-graph-duplicates.yml @@ -0,0 +1,4 @@ +--- +title: group authors in contribution graph with case insensitive email handle comparison +merge_request: 8021 +author: -- cgit v1.2.1 From bc31040ea9344e8bc4d3c90fa49a6e30ce9c3698 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Sat, 10 Dec 2016 12:04:54 +0000 Subject: Fixed lint warning and propose fail or warning --- package.json | 2 +- spec/javascripts/project_title_spec.js | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 961989f8012..49b8210e427 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "eslint": "eslint --ext .js,.js.es6 .", + "eslint": "eslint --max-warnings 0 --ext .js,.js.es6 .", "eslint-fix": "npm run eslint -- --fix", "eslint-report": "npm run eslint -- --format html --output-file ./eslint-report.html" }, diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index 49211a6b852..65de1756201 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -21,16 +21,18 @@ return this.project = new Project(); }); return describe('project list', function() { + var fakeAjaxResponse = function fakeAjaxResponse(req) { + var d; + expect(req.url).toBe('/api/v3/projects.json?simple=true'); + d = $.Deferred(); + d.resolve(this.projects_data); + return d.promise(); + }; + beforeEach((function(_this) { return function() { _this.projects_data = fixture.load('projects.json')[0]; - return spyOn(jQuery, 'ajax').and.callFake(function(req) { - var d; - expect(req.url).toBe('/api/v3/projects.json?simple=true'); - d = $.Deferred(); - d.resolve(_this.projects_data); - return d.promise(); - }); + return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this)); }; })(this)); it('to show on toggle click', (function(_this) { -- cgit v1.2.1 From d8b7df3cbcfd4fcdf204fdcba01720e60fb598bf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 8 Dec 2016 20:59:41 +0200 Subject: Add support for nested groups to admin routing Signed-off-by: Dmitriy Zaporozhets --- app/views/admin/dashboard/_head.html.haml | 2 +- app/views/admin/dashboard/index.html.haml | 2 +- app/views/admin/groups/_group.html.haml | 2 +- app/views/admin/groups/show.html.haml | 4 ++-- app/views/admin/projects/index.html.haml | 12 +++++------ app/views/repository_check_mailer/notify.html.haml | 2 +- app/views/repository_check_mailer/notify.text.haml | 2 +- config/routes/admin.rb | 23 +++++++++++++++------- features/steps/shared/paths.rb | 2 +- spec/features/admin/admin_groups_spec.rb | 2 +- spec/features/admin/admin_projects_spec.rb | 6 +++--- spec/features/security/admin_access_spec.rb | 2 +- spec/routing/admin_routing_spec.rb | 14 ++++++++++++- 13 files changed, 48 insertions(+), 27 deletions(-) diff --git a/app/views/admin/dashboard/_head.html.haml b/app/views/admin/dashboard/_head.html.haml index ec40391a3e3..b5f96363230 100644 --- a/app/views/admin/dashboard/_head.html.haml +++ b/app/views/admin/dashboard/_head.html.haml @@ -8,7 +8,7 @@ %span Overview = nav_link(controller: [:admin, :projects]) do - = link_to admin_namespaces_projects_path, title: 'Projects' do + = link_to admin_projects_path, title: 'Projects' do %span Projects = nav_link(controller: :users) do diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index e51f4ac1d93..5238623e936 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -116,7 +116,7 @@ .light-well.well-centered %h4 Projects .data - = link_to admin_namespaces_projects_path do + = link_to admin_projects_path do %h1= number_with_delimiter(Project.cached_count) %hr = link_to('New Project', new_project_path, class: "btn btn-new") diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 664bb417c6a..4efeec0ea4e 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -2,7 +2,7 @@ %li.group-row{ class: css_class } .controls - = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn' + = link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn' = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove' .stats %span diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 40871e32913..71a605f33b1 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -2,7 +2,7 @@ %h3.page-title Group: #{@group.name} - = link_to edit_admin_group_path(@group), class: "btn pull-right" do + = link_to admin_group_edit_path(@group), class: "btn pull-right" do %i.fa.fa-pencil-square-o Edit %hr @@ -88,7 +88,7 @@ Read more about project permissions %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" - = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do + = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all) %div.prepend-top-10 diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index b37b8d4fee7..8bc7dc7dd51 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -7,7 +7,7 @@ %div{ class: container_class } .top-area .prepend-top-default - = form_tag admin_namespaces_projects_path, method: :get do |f| + = form_tag admin_projects_path, method: :get do |f| .search-holder .search-field-holder = search_field_tag :name, params[:name], class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false, placeholder: 'Search by name' @@ -41,19 +41,19 @@ = button_tag "Search", class: "btn btn-primary btn-search" %ul.nav-links - - opts = params[:visibility_level].present? ? {} : { page: admin_namespaces_projects_path } + - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } = nav_link(opts) do - = link_to admin_namespaces_projects_path do + = link_to admin_projects_path do All = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s ? 'active' : '' }) do - = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do Private = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s ? 'active' : '' }) do - = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do Internal = nav_link(html_options: { class: params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s ? 'active' : '' }) do - = link_to admin_namespaces_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do Public .nav-controls diff --git a/app/views/repository_check_mailer/notify.html.haml b/app/views/repository_check_mailer/notify.html.haml index a585147ddd1..94e5a5d9709 100644 --- a/app/views/repository_check_mailer/notify.html.haml +++ b/app/views/repository_check_mailer/notify.html.haml @@ -2,7 +2,7 @@ #{@message}. %p - = link_to "See the affected projects in the GitLab admin panel", admin_namespaces_projects_url(last_repository_check_failed: 1) + = link_to "See the affected projects in the GitLab admin panel", admin_projects_url(last_repository_check_failed: 1) %p You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}. diff --git a/app/views/repository_check_mailer/notify.text.haml b/app/views/repository_check_mailer/notify.text.haml index 93db151329e..0902c50d052 100644 --- a/app/views/repository_check_mailer/notify.text.haml +++ b/app/views/repository_check_mailer/notify.text.haml @@ -1,6 +1,6 @@ #{@message}. \ -View details: #{admin_namespaces_projects_url(last_repository_check_failed: 1)} +View details: #{admin_projects_url(last_repository_check_failed: 1)} You are receiving this message because you are a GitLab administrator for #{Gitlab.config.gitlab.url}. diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 5ae985da561..0dd2c8f7aef 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -28,9 +28,19 @@ namespace :admin do resources :applications - resources :groups, constraints: { id: /[^\/]+/ } do - member do + resources :groups, only: [:index, :new, :create] + + scope(path: 'groups/*id', + controller: :groups, + constraints: { id: Gitlab::Regex.namespace_route_regex }) do + + scope(as: :group) do put :members_update + get :edit, action: :edit + get '/', action: :show + patch '/', action: :update + put '/', action: :update + delete '/', action: :destroy end end @@ -50,14 +60,13 @@ namespace :admin do resource :system_info, controller: 'system_info', only: [:show] resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } - resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do - root to: 'projects#index', as: :projects + resources :projects, only: [:index] + scope(path: 'projects/*namespace_id', as: :namespace) do resources(:projects, path: '/', - constraints: { id: /[a-zA-Z.0-9_\-]+/ }, - only: [:index, :show]) do - root to: 'projects#show' + constraints: { id: Gitlab::Regex.project_route_regex }, + only: [:show]) do member do put :transfer diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 2bd8ea745e4..398160449ca 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -168,7 +168,7 @@ module SharedPaths end step 'I visit admin projects page' do - visit admin_namespaces_projects_path + visit admin_projects_path end step 'I visit admin users page' do diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index f6d625fa7f6..0aa01fc499a 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -21,7 +21,7 @@ feature 'Admin Groups', feature: true do scenario 'shows the visibility level radio populated with the group visibility_level value' do group = create(:group, :private) - visit edit_admin_group_path(group) + visit admin_group_edit_path(group) expect_selected_visibility(group.visibility_level) end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 30ded9202a4..a36bfd574cb 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -8,11 +8,11 @@ describe "Admin::Projects", feature: true do describe "GET /admin/projects" do before do - visit admin_namespaces_projects_path + visit admin_projects_path end it "is ok" do - expect(current_path).to eq(admin_namespaces_projects_path) + expect(current_path).to eq(admin_projects_path) end it "has projects list" do @@ -22,7 +22,7 @@ describe "Admin::Projects", feature: true do describe "GET /admin/projects/:id" do before do - visit admin_namespaces_projects_path + visit admin_projects_path click_link "#{@project.name}" end diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb index fe8cd7b7602..e180ca53eb5 100644 --- a/spec/features/security/admin_access_spec.rb +++ b/spec/features/security/admin_access_spec.rb @@ -4,7 +4,7 @@ describe "Admin::Projects", feature: true do include AccessMatchers describe "GET /admin/projects" do - subject { admin_namespaces_projects_path } + subject { admin_projects_path } it { is_expected.to be_allowed_for :admin } it { is_expected.to be_denied_for :user } diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 69eeb45ed71..661b671301e 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -66,7 +66,8 @@ describe Admin::ProjectsController, "routing" do end it "to #show" do - expect(get("/admin/projects/gitlab")).to route_to('admin/projects#show', namespace_id: 'gitlab') + expect(get("/admin/projects/gitlab/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab', id: 'gitlab-ce') + expect(get("/admin/projects/gitlab/subgroup/gitlab-ce")).to route_to('admin/projects#show', namespace_id: 'gitlab/subgroup', id: 'gitlab-ce') end end @@ -119,3 +120,14 @@ describe Admin::HealthCheckController, "routing" do expect(get("/admin/health_check")).to route_to('admin/health_check#show') end end + +describe Admin::GroupsController, "routing" do + it "to #index" do + expect(get("/admin/groups")).to route_to('admin/groups#index') + end + + it "to #show" do + expect(get("/admin/groups/gitlab")).to route_to('admin/groups#show', id: 'gitlab') + expect(get("/admin/groups/gitlab/subgroup")).to route_to('admin/groups#show', id: 'gitlab/subgroup') + end +end -- cgit v1.2.1 From b552a4eb18ad1f3e8b9c0a4e56898a36d3d9d8de Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Dec 2016 19:16:02 +0200 Subject: Validate presence of route by Routable concern Signed-off-by: Dmitriy Zaporozhets --- app/models/concerns/routable.rb | 1 + spec/models/concerns/routable_spec.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index d36bb9da296..8d377484473 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -7,6 +7,7 @@ module Routable has_one :route, as: :source, autosave: true, dependent: :destroy validates_associated :route + validates :route, presence: true before_validation :update_route_path, if: :full_path_changed? end diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index 0acefc0c1d5..d6e93b4bf7b 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -3,6 +3,10 @@ require 'spec_helper' describe Group, 'Routable' do let!(:group) { create(:group) } + describe 'Validations' do + it { is_expected.to validate_presence_of(:route) } + end + describe 'Associations' do it { is_expected.to have_one(:route).dependent(:destroy) } end -- cgit v1.2.1 From d7010fdcd7c73c32c71cc4c635472014597cee81 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 7 Dec 2016 19:16:17 +0200 Subject: Create nested group factory Signed-off-by: Dmitriy Zaporozhets --- spec/factories/groups.rb | 4 ++++ spec/models/group_spec.rb | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb index ebd3595ea64..ece6beb9fa9 100644 --- a/spec/factories/groups.rb +++ b/spec/factories/groups.rb @@ -19,5 +19,9 @@ FactoryGirl.define do trait :access_requestable do request_access_enabled true end + + trait :nested do + parent factory: :group + end end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 1613a586a2c..850b1a3cf1e 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -271,4 +271,11 @@ describe Group, models: true do expect(group.web_url).to include("groups/#{group.name}") end end + + describe 'nested group' do + subject { create(:group, :nested) } + + it { is_expected.to be_valid } + it { expect(subject.parent).to be_kind_of(Group) } + end end -- cgit v1.2.1 From 3e0b8e000f088c24493e993139f8af1be2e14de1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 9 Dec 2016 17:27:11 +0200 Subject: Rename Routable.where_paths_in to Routable.where_full_path_in Signed-off-by: Dmitriy Zaporozhets --- app/models/concerns/routable.rb | 6 +++--- lib/banzai/filter/abstract_reference_filter.rb | 2 +- spec/models/concerns/routable_spec.rb | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index 8d377484473..1108a64c59e 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -29,17 +29,17 @@ module Routable order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)" - where_paths_in([path]).reorder(order_sql).take + where_full_path_in([path]).reorder(order_sql).take end # Builds a relation to find multiple objects by their full paths. # # Usage: # - # Klass.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) + # Klass.where_full_path_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) # # Returns an ActiveRecord::Relation. - def where_paths_in(paths) + def where_full_path_in(paths) wheres = [] cast_lower = Gitlab::Database.postgresql? diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb index d904a8bd4ae..fd74eeaebe7 100644 --- a/lib/banzai/filter/abstract_reference_filter.rb +++ b/lib/banzai/filter/abstract_reference_filter.rb @@ -248,7 +248,7 @@ module Banzai end def projects_relation_for_paths(paths) - Project.where_paths_in(paths).includes(:namespace) + Project.where_full_path_in(paths).includes(:namespace) end # Returns projects for the given paths. diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb index d6e93b4bf7b..b556135532f 100644 --- a/spec/models/concerns/routable_spec.rb +++ b/spec/models/concerns/routable_spec.rb @@ -39,16 +39,16 @@ describe Group, 'Routable' do it { expect(described_class.find_by_full_path('unknown')).to eq(nil) } end - describe '.where_paths_in' do + describe '.where_full_path_in' do context 'without any paths' do it 'returns an empty relation' do - expect(described_class.where_paths_in([])).to eq([]) + expect(described_class.where_full_path_in([])).to eq([]) end end context 'without any valid paths' do it 'returns an empty relation' do - expect(described_class.where_paths_in(%w[unknown])).to eq([]) + expect(described_class.where_full_path_in(%w[unknown])).to eq([]) end end @@ -56,13 +56,13 @@ describe Group, 'Routable' do let!(:nested_group) { create(:group, parent: group) } it 'returns the projects matching the paths' do - result = described_class.where_paths_in([group.to_param, nested_group.to_param]) + result = described_class.where_full_path_in([group.to_param, nested_group.to_param]) expect(result).to contain_exactly(group, nested_group) end it 'returns projects regardless of the casing of paths' do - result = described_class.where_paths_in([group.to_param.upcase, nested_group.to_param.upcase]) + result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase]) expect(result).to contain_exactly(group, nested_group) end -- cgit v1.2.1 From e71ed01bfbaef088e8f7927b01d5c470b4cfc5f2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 11 Dec 2016 10:18:44 +0100 Subject: Change docs title to represent the edition --- doc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/README.md b/doc/README.md index 66c8c26e4f0..eba1e9845b1 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,4 +1,4 @@ -# Documentation +# GitLab Community Edition documentation ## User documentation -- cgit v1.2.1 From d6a1a3e6bee6417d02a2095bce479f494460ce17 Mon Sep 17 00:00:00 2001 From: Ismail S Date: Sun, 11 Dec 2016 14:14:59 +0000 Subject: Fix typo --- doc/ci/review_apps/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md index a66165dc973..c679ea4e298 100644 --- a/doc/ci/review_apps/index.md +++ b/doc/ci/review_apps/index.md @@ -33,7 +33,7 @@ built and deployed under a dynamic environment and can be previewed with an also dynamically URL. The details of the Review Apps implementation depend widely on your real -technology stack and on your deployment process. The simplest case it to +technology stack and on your deployment process. The simplest case is to deploy a simple static HTML website, but it will not be that straightforward when your app is using a database for example. To make a branch be deployed on a temporary instance and booting up this instance with all required software -- cgit v1.2.1 From 36c24de5da7feb6392e8250e2d9e11e602d3e527 Mon Sep 17 00:00:00 2001 From: Georg Hartmann Date: Sun, 11 Dec 2016 19:36:01 +0000 Subject: Fix typo in curl example request --- doc/api/merge_requests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 81df55ab4ab..662cc9da733 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -429,7 +429,7 @@ DELETE /projects/:id/merge_requests/:merge_request_id | `merge_request_id` | integer | yes | The ID of a project's merge request | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_request/85 +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/merge_requests/85 ``` ## Accept MR -- cgit v1.2.1 From e117cb3a1965c15d3d5617addb695f337dcf069f Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sun, 11 Dec 2016 23:22:18 -0700 Subject: Update Sidekiq from 4.2.1 to 4.2.7. Includes various bug fixes, mostly for Rails 5. Changelog: https://github.com/mperham/sidekiq/blob/fc168fe393bee3ad1fcbb52cff2d84af86c38cc4/Changes.md --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index f27d6363e3d..2cc7764e6b8 100644 --- a/Gemfile +++ b/Gemfile @@ -132,7 +132,7 @@ gem 'after_commit_queue', '~> 1.3.0' gem 'acts-as-taggable-on', '~> 4.0' # Background jobs -gem 'sidekiq', '~> 4.2' +gem 'sidekiq', '~> 4.2.7' gem 'sidekiq-cron', '~> 0.4.4' gem 'redis-namespace', '~> 1.5.2' gem 'sidekiq-limit_fetch', '~> 3.4' diff --git a/Gemfile.lock b/Gemfile.lock index c464ff70587..3de1a7cbf26 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -126,7 +126,7 @@ GEM coffee-script-source (1.10.0) colorize (0.7.7) concurrent-ruby (1.0.2) - connection_pool (2.2.0) + connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) creole (0.5.0) @@ -648,10 +648,10 @@ GEM rack shoulda-matchers (2.8.0) activesupport (>= 3.0.0) - sidekiq (4.2.1) + sidekiq (4.2.7) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) - rack-protection (~> 1.5) + rack-protection (>= 1.5.0) redis (~> 3.2, >= 3.2.1) sidekiq-cron (0.4.4) redis-namespace (>= 1.5.2) @@ -928,7 +928,7 @@ DEPENDENCIES settingslogic (~> 2.0.9) sham_rack (~> 1.3.6) shoulda-matchers (~> 2.8.0) - sidekiq (~> 4.2) + sidekiq (~> 4.2.7) sidekiq-cron (~> 0.4.4) sidekiq-limit_fetch (~> 3.4) simplecov (= 0.12.0) -- cgit v1.2.1 From 8d5f7e26cd63e60fbcd1cbe6f5e6b77f90119481 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 2 Dec 2016 21:46:50 +0600 Subject: adds check for logged in user in group issues --- app/views/groups/issues.html.haml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 324a116a50e..872d70d17d9 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -6,13 +6,14 @@ - if group_issues(@group).exists? .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls - - if current_user - = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do - = icon('rss') - %span.icon-label - Subscribe - = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" + - if current_user + .nav-controls + - if current_user + = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do + = icon('rss') + %span.icon-label + Subscribe + = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/issuable/filter', type: :issues -- cgit v1.2.1 From fd20d0b19e17603c886189148e9f59caab50ae19 Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Fri, 2 Dec 2016 21:55:56 +0600 Subject: adds changelog --- changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml diff --git a/changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml b/changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml new file mode 100644 index 00000000000..62030d3fc45 --- /dev/null +++ b/changelogs/unreleased/25106-hide-issue-mr-button-for-not-loggedin.yml @@ -0,0 +1,4 @@ +--- +title: Prevent user creating issue or MR without signing in for a group +merge_request: 7902 +author: -- cgit v1.2.1 From 0bde5c946ab31f822e1146b0e242fdbd1553733e Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Mon, 5 Dec 2016 13:51:57 +0600 Subject: hides new MR button from not loggedin user --- app/views/groups/merge_requests.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index e6953d94531..dbbdb583a24 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -2,8 +2,9 @@ .top-area = render 'shared/issuable/nav', type: :merge_requests - .nav-controls - = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" + - if current_user + .nav-controls + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/issuable/filter', type: :merge_requests -- cgit v1.2.1 From c877b152d1354946d0a1299e672c1be0aaafdfdf Mon Sep 17 00:00:00 2001 From: Nur Rony Date: Mon, 12 Dec 2016 13:02:08 +0600 Subject: removes extra if check --- app/views/groups/issues.html.haml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 872d70d17d9..b4aa4f24d9e 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -8,11 +8,10 @@ = render 'shared/issuable/nav', type: :issues - if current_user .nav-controls - - if current_user - = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do - = icon('rss') - %span.icon-label - Subscribe + = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do + = icon('rss') + %span.icon-label + Subscribe = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/issuable/filter', type: :issues -- cgit v1.2.1 From 3c36d9dc9b3c2db45df6dce19357e9c4bdde366f Mon Sep 17 00:00:00 2001 From: jnoortheen Date: Tue, 6 Dec 2016 22:32:30 +0530 Subject: fix: removed signed_out notification test: replaced signed_out message check with check for sign_in button fixes #25294 --- app/controllers/sessions_controller.rb | 6 ++++++ changelogs/unreleased/25294-remove-signed-out-msg.yml | 4 ++++ spec/support/login_helpers.rb | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/25294-remove-signed-out-msg.yml diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 38e7c6f4a48..8c698695202 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -37,6 +37,12 @@ class SessionsController < Devise::SessionsController end end + def destroy + super + # hide the signed_out notice + flash[:notice] = nil + end + private # Handle an "initial setup" state, where there's only one user, it's an admin, diff --git a/changelogs/unreleased/25294-remove-signed-out-msg.yml b/changelogs/unreleased/25294-remove-signed-out-msg.yml new file mode 100644 index 00000000000..567294fe5f7 --- /dev/null +++ b/changelogs/unreleased/25294-remove-signed-out-msg.yml @@ -0,0 +1,4 @@ +--- +title: 'fix: removed signed_out notification' +merge_request: 7958 +author: jnoortheen diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index c0b3e83244d..ad1eed5b369 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -75,7 +75,8 @@ module LoginHelpers def logout find(".header-user-dropdown-toggle").click click_link "Sign out" - expect(page).to have_content('Signed out successfully') + # check the sign_in button + expect(page).to have_button('Sign in') end # Logout without JavaScript driver -- cgit v1.2.1 From 633e64382d30674fecb1aaf138d18c73827c9a40 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 09:51:36 +0100 Subject: Added Ci::Status::Build --- app/models/ci/build.rb | 4 +++ app/models/commit_status.rb | 4 +++ lib/gitlab/ci/status/build/common.rb | 54 +++++++++++++++++++++++++++++++++++ lib/gitlab/ci/status/build/factory.rb | 15 ++++++++++ lib/gitlab/ci/status/core.rb | 10 +++++-- 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 lib/gitlab/ci/status/build/common.rb create mode 100644 lib/gitlab/ci/status/build/factory.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 88c46076df6..0f4c498c266 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -100,6 +100,10 @@ module Ci end end + def detailed_status + Gitlab::Ci::Status::Build::Factory.new(self).fabricate! + end + def manual? self.when == 'manual' end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index cf90475f4d4..fce16174e22 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -131,4 +131,8 @@ class CommitStatus < ActiveRecord::Base def has_trace? false end + + def detailed_status + Gitlab::Ci::Status::Factory.new(self).fabricate! + end end diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb new file mode 100644 index 00000000000..d3d7e03ee3f --- /dev/null +++ b/lib/gitlab/ci/status/build/common.rb @@ -0,0 +1,54 @@ +module Gitlab + module Ci + module Status + module Build + module Common + def has_details? + true + end + + def details_path + namespace_project_build_path(@subject.project.namespace, + @subject.project, + @subject.pipeline) + end + + def action_type + case + when @subject.playable? then :playable + when @subject.active? then :cancel + when @subject.retryable? then :retry + end + end + + def has_action?(current_user) + action_type && can?(current_user, :update_build, @subject) + end + + def action_icon + case action_type + when :playable then 'remove' + when :cancel then 'icon_play' + when :retry then 'repeat' + end + end + + def action_path + case action_type + when :playable + play_namespace_project_build_path(subject.project.namespace, subject.project, subject) + when :cancel + cancel_namespace_project_build_path(subject.project.namespace, subject.project, subject) + when :retry + retry_namespace_project_build_path(subject.project.namespace, subject.project, subject) + end + end + + def action_method + :post + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb new file mode 100644 index 00000000000..dd38e1418b6 --- /dev/null +++ b/lib/gitlab/ci/status/build/factory.rb @@ -0,0 +1,15 @@ +module Gitlab + module Ci + module Status + module Build + class Factory < Status::Factory + private + + def core_status + super.extend(Status::Build::Common) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index ce4108fdcf2..60c559248aa 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -34,15 +34,15 @@ module Gitlab end def has_details? - raise NotImplementedError + false end def details_path raise NotImplementedError end - def has_action? - raise NotImplementedError + def has_action?(_user = nil) + false end def action_icon @@ -52,6 +52,10 @@ module Gitlab def action_path raise NotImplementedError end + + def action_method + raise NotImplementedError + end end end end -- cgit v1.2.1 From 516dc7a5be3624f1866fa46f19f6472e2f9fae22 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 10:40:56 +0100 Subject: Improve actions --- app/models/ci/build.rb | 4 +++ lib/gitlab/ci/status/build/common.rb | 26 ++++++------------- lib/gitlab/ci/status/build/factory.rb | 4 +++ lib/gitlab/ci/status/build/play.rb | 47 +++++++++++++++++++++++++++++++++++ lib/gitlab/ci/status/build/stop.rb | 47 +++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 lib/gitlab/ci/status/build/play.rb create mode 100644 lib/gitlab/ci/status/build/stop.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 0f4c498c266..73564dd2aa0 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -127,6 +127,10 @@ module Ci end end + def cancelable? + active? + end + def retryable? project.builds_enabled? && commands.present? && complete? end diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb index d3d7e03ee3f..2bed68d1a11 100644 --- a/lib/gitlab/ci/status/build/common.rb +++ b/lib/gitlab/ci/status/build/common.rb @@ -13,33 +13,23 @@ module Gitlab @subject.pipeline) end - def action_type - case - when @subject.playable? then :playable - when @subject.active? then :cancel - when @subject.retryable? then :retry - end - end - def has_action?(current_user) - action_type && can?(current_user, :update_build, @subject) + (subject.cancelable? || subject.retryable?) && + can?(current_user, :update_build, @subject) end def action_icon - case action_type - when :playable then 'remove' - when :cancel then 'icon_play' - when :retry then 'repeat' + case + when subject.cancelable? then 'icon_play' + when subject.retryable? then 'repeat' end end def action_path - case action_type - when :playable - play_namespace_project_build_path(subject.project.namespace, subject.project, subject) - when :cancel + case + when subject.cancelable? cancel_namespace_project_build_path(subject.project.namespace, subject.project, subject) - when :retry + when subject.retryable? retry_namespace_project_build_path(subject.project.namespace, subject.project, subject) end end diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index dd38e1418b6..d8a9f53f236 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -5,6 +5,10 @@ module Gitlab class Factory < Status::Factory private + def extended_statuses + [Stop, Play] + end + def core_status super.extend(Status::Build::Common) end diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb new file mode 100644 index 00000000000..581c81d0175 --- /dev/null +++ b/lib/gitlab/ci/status/build/play.rb @@ -0,0 +1,47 @@ +module Gitlab + module Ci + module Status + module Status + class Play < SimpleDelegator + extend Status::Extended + + def text + 'play' + end + + def label + 'play' + end + + def icon + 'icon_status_skipped' + end + + def to_s + 'play' + end + + def has_action?(current_user) + can?(current_user, :update_build, subject) + end + + def action_icon + :play + end + + def action_path + play_namespace_project_build_path(subject.project.namespace, subject.project, subject) + end + + def action_method + :post + end + + def self.matches?(build) + build.playable? && !build.stops_environment? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb new file mode 100644 index 00000000000..261de9695c5 --- /dev/null +++ b/lib/gitlab/ci/status/build/stop.rb @@ -0,0 +1,47 @@ +module Gitlab + module Ci + module Status + module Status + class Play < SimpleDelegator + extend Status::Extended + + def text + 'stop' + end + + def label + 'stop' + end + + def icon + 'icon_status_skipped' + end + + def to_s + 'stop' + end + + def has_action?(current_user) + can?(current_user, :update_build, subject) + end + + def action_icon + :play + end + + def action_path + play_namespace_project_build_path(subject.project.namespace, subject.project, subject) + end + + def action_method + :post + end + + def self.matches?(build) + build.playable? && build.stops_environment? + end + end + end + end + end +end -- cgit v1.2.1 From 1b6c2c3c0a38bed733d861902eb8c9397ab76cd3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 10:48:08 +0100 Subject: Introduce `cancelable` and `returnable` [ci skip] --- lib/gitlab/ci/status/build/cancelable.rb | 31 +++++++++++++++++++++++++++++++ lib/gitlab/ci/status/build/common.rb | 25 ------------------------- lib/gitlab/ci/status/build/factory.rb | 2 +- lib/gitlab/ci/status/build/play.rb | 10 +--------- lib/gitlab/ci/status/build/retryable.rb | 31 +++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 35 deletions(-) create mode 100644 lib/gitlab/ci/status/build/cancelable.rb create mode 100644 lib/gitlab/ci/status/build/retryable.rb diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb new file mode 100644 index 00000000000..bff0464ef0c --- /dev/null +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -0,0 +1,31 @@ +module Gitlab + module Ci + module Status + module Status + class Cancelable < SimpleDelegator + extend Status::Extended + + def has_action?(current_user) + can?(current_user, :update_build, subject) + end + + def action_icon + 'remove' + end + + def action_path + cancel_namespace_project_build_path(subject.project.namespace, subject.project, subject) + end + + def action_method + :post + end + + def self.matches?(build) + build.cancelable? + end + end + end + end + end +end diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb index 2bed68d1a11..3e47d7dfd20 100644 --- a/lib/gitlab/ci/status/build/common.rb +++ b/lib/gitlab/ci/status/build/common.rb @@ -12,31 +12,6 @@ module Gitlab @subject.project, @subject.pipeline) end - - def has_action?(current_user) - (subject.cancelable? || subject.retryable?) && - can?(current_user, :update_build, @subject) - end - - def action_icon - case - when subject.cancelable? then 'icon_play' - when subject.retryable? then 'repeat' - end - end - - def action_path - case - when subject.cancelable? - cancel_namespace_project_build_path(subject.project.namespace, subject.project, subject) - when subject.retryable? - retry_namespace_project_build_path(subject.project.namespace, subject.project, subject) - end - end - - def action_method - :post - end end end end diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index d8a9f53f236..8f420a93954 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -6,7 +6,7 @@ module Gitlab private def extended_statuses - [Stop, Play] + [Stop, Play, Cancelable, Retryable] end def core_status diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index 581c81d0175..d295850137b 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -13,20 +13,12 @@ module Gitlab 'play' end - def icon - 'icon_status_skipped' - end - - def to_s - 'play' - end - def has_action?(current_user) can?(current_user, :update_build, subject) end def action_icon - :play + 'play' end def action_path diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb new file mode 100644 index 00000000000..b3c0eedadf8 --- /dev/null +++ b/lib/gitlab/ci/status/build/retryable.rb @@ -0,0 +1,31 @@ +module Gitlab + module Ci + module Status + module Status + class Retryable < SimpleDelegator + extend Status::Extended + + def has_action?(current_user) + can?(current_user, :update_build, subject) + end + + def action_icon + 'repeat' + end + + def action_path + retry_namespace_project_build_path(subject.project.namespace, subject.project, subject) + end + + def action_method + :post + end + + def self.matches?(build) + build.retryable? + end + end + end + end + end +end -- cgit v1.2.1 From a83a80edb368d9b5697493123c2f13d8b7c6531e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 10:52:44 +0100 Subject: Check permission of details --- lib/gitlab/ci/status/build/common.rb | 4 ++-- lib/gitlab/ci/status/core.rb | 2 +- lib/gitlab/ci/status/pipeline/common.rb | 4 ++-- lib/gitlab/ci/status/stage/common.rb | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb index 3e47d7dfd20..2fb79afa3d3 100644 --- a/lib/gitlab/ci/status/build/common.rb +++ b/lib/gitlab/ci/status/build/common.rb @@ -3,8 +3,8 @@ module Gitlab module Status module Build module Common - def has_details? - true + def has_details?(current_user) + can?(current_user, :read_build, subject) end def details_path diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 60c559248aa..6b47096f811 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -33,7 +33,7 @@ module Gitlab self.class.name.demodulize.downcase.underscore end - def has_details? + def has_details?(_user = nil) false end diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb index 25e52bec3da..5f79044a496 100644 --- a/lib/gitlab/ci/status/pipeline/common.rb +++ b/lib/gitlab/ci/status/pipeline/common.rb @@ -3,8 +3,8 @@ module Gitlab module Status module Pipeline module Common - def has_details? - true + def has_details?(current_user) + can?(current_user, :read_pipeline, subject) end def details_path diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb index 14c437d2b98..e6ee2f92341 100644 --- a/lib/gitlab/ci/status/stage/common.rb +++ b/lib/gitlab/ci/status/stage/common.rb @@ -3,8 +3,8 @@ module Gitlab module Status module Stage module Common - def has_details? - true + def has_details?(current_user) + can?(current_user, :read_pipeline, subject) end def details_path -- cgit v1.2.1 From feaf01802c092be8f55994c910f2975376cbd20f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 11:03:01 +0100 Subject: Remove ci_status_with_icon helper and replace it with partial [ci skip] --- app/helpers/ci_status_helper.rb | 20 +------------------- app/views/admin/runners/show.html.haml | 2 +- app/views/ci/status/_icon_with_label.html.haml | 10 ++++++++++ app/views/projects/builds/_header.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 5 +---- app/views/projects/ci/pipelines/_pipeline.html.haml | 5 +---- .../_generic_commit_status.html.haml | 5 +---- app/views/projects/pipelines/_info.html.haml | 2 +- 8 files changed, 17 insertions(+), 34 deletions(-) create mode 100644 app/views/ci/status/_icon_with_label.html.haml diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 8e19752a8a1..d9f5e01f0dc 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -4,25 +4,7 @@ module CiStatusHelper builds_namespace_project_commit_path(project.namespace, project, pipeline.sha) end - def ci_status_with_icon(status, target = nil) - content = ci_icon_for_status(status) + ci_text_for_status(status) - klass = "ci-status ci-#{status}" - - if target - link_to content, target, class: klass - else - content_tag :span, content, class: klass - end - end - - def ci_text_for_status(status) - if detailed_status?(status) - status.text - else - status - end - end - + # Is used by Commit and Merge Request Widget def ci_label_for_status(status) if detailed_status?(status) return status.label diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 73038164056..fa8be25ffa8 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -91,7 +91,7 @@ %strong ##{build.id} %td.status - = ci_status_with_icon(build.status) + = render "ci/status/icon_with_label", subject: build %td.status - if project diff --git a/app/views/ci/status/_icon_with_label.html.haml b/app/views/ci/status/_icon_with_label.html.haml new file mode 100644 index 00000000000..65a74e88444 --- /dev/null +++ b/app/views/ci/status/_icon_with_label.html.haml @@ -0,0 +1,10 @@ +- details_path = subject.details_path if subject.has_details?(current_user) +- klass = "ci-status ci-#{subject.status}" +- if details_path + = link_to details_path, class: klass do + = custom_icon(status.icon) + = status.text +- else + %span{ class: klass } + = custom_icon(status.icon) + = status.text diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index f6aa20c4579..5e4e30f08d5 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,6 +1,6 @@ .content-block.build-header .header-content - = ci_status_with_icon(@build.status) + = render "ci/status/icon_with_label", subject: build Build %strong ##{@build.id} in pipeline diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 18b3b04154f..6b0cd3e49a0 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -9,10 +9,7 @@ %tr.build.commit{class: ('retried' if retried)} %td.status - - if can?(current_user, :read_build, build) - = ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build)) - - else - = ci_status_with_icon(build.status) + = render "ci/status/icon_with_label", subject: build %td.branch-commit - if can?(current_user, :read_build, build) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index b58dceb58c9..84243e4306d 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -1,13 +1,10 @@ - status = pipeline.status -- detailed_status = pipeline.detailed_status - show_commit = local_assigns.fetch(:show_commit, true) - show_branch = local_assigns.fetch(:show_branch, true) %tr.commit %td.commit-link - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do - = ci_icon_for_status(detailed_status) - = ci_text_for_status(detailed_status) + = render "ci/status/icon_with_label", subject: pipeline %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 7f751d9ae2e..69cb1631ee8 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -8,10 +8,7 @@ %tr.generic_commit_status{class: ('retried' if retried)} %td.status - - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url - = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url) - - else - = ci_status_with_icon(generic_commit_status.status) + = render "ci/status/icon_with_label", subject: generic_commit_status %td.generic_commit_status-link - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 229bdfb0e8d..f7385184a2b 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,6 +1,6 @@ .page-content-header .header-main-content - = ci_status_with_icon(@pipeline.detailed_status) + = render "ci/status/icon_with_label", subject: @pipeline %strong Pipeline ##{@commit.pipelines.last.id} triggered #{time_ago_with_tooltip(@commit.authored_date)} by = author_avatar(@commit, size: 24) -- cgit v1.2.1 From e0ce97fb7d7d995fa76df57bfaac6d3601800190 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 8 Dec 2016 13:51:06 +0100 Subject: Refactor ci status factories to DRY code a little --- lib/gitlab/ci/status/build/factory.rb | 11 +++++------ lib/gitlab/ci/status/extended.rb | 2 +- lib/gitlab/ci/status/factory.rb | 30 +++++++++++++++++------------- lib/gitlab/ci/status/pipeline/factory.rb | 8 +++----- lib/gitlab/ci/status/stage/factory.rb | 6 ++---- 5 files changed, 28 insertions(+), 29 deletions(-) diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb index 8f420a93954..eee9a64120b 100644 --- a/lib/gitlab/ci/status/build/factory.rb +++ b/lib/gitlab/ci/status/build/factory.rb @@ -3,14 +3,13 @@ module Gitlab module Status module Build class Factory < Status::Factory - private - - def extended_statuses - [Stop, Play, Cancelable, Retryable] + def self.extended_statuses + [Status::Build::Stop, Status::Build::Play, + Status::Build::Cancelable, Status::Build::Retryable] end - def core_status - super.extend(Status::Build::Common) + def self.common_helpers + Status::Build::Common end end end diff --git a/lib/gitlab/ci/status/extended.rb b/lib/gitlab/ci/status/extended.rb index 6bfb5d38c1f..93e6eff1c94 100644 --- a/lib/gitlab/ci/status/extended.rb +++ b/lib/gitlab/ci/status/extended.rb @@ -2,7 +2,7 @@ module Gitlab module Ci module Status module Extended - def matches?(_subject) + def matches?(_subject, _user) raise NotImplementedError end end diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb index b2f896f2211..944e0fdde2d 100644 --- a/lib/gitlab/ci/status/factory.rb +++ b/lib/gitlab/ci/status/factory.rb @@ -2,10 +2,9 @@ module Gitlab module Ci module Status class Factory - attr_reader :subject - - def initialize(subject) + def initialize(subject, user = nil) @subject = subject + @user = user end def fabricate! @@ -16,27 +15,32 @@ module Gitlab end end + def self.extended_statuses + [] + end + + def self.common_helpers + Module.new + end + private - def subject_status - @subject_status ||= subject.status + def simple_status + @simple_status ||= @subject.status || :created end def core_status Gitlab::Ci::Status - .const_get(subject_status.capitalize) - .new(subject) + .const_get(simple_status.capitalize) + .new(@subject) + .extend(self.class.common_helpers) end def extended_status - @extended ||= extended_statuses.find do |status| - status.matches?(subject) + @extended ||= self.class.extended_statuses.find do |status| + status.matches?(@subject, @user) end end - - def extended_statuses - [] - end end end end diff --git a/lib/gitlab/ci/status/pipeline/factory.rb b/lib/gitlab/ci/status/pipeline/factory.rb index 4ac4ec671d0..16dcb326be9 100644 --- a/lib/gitlab/ci/status/pipeline/factory.rb +++ b/lib/gitlab/ci/status/pipeline/factory.rb @@ -3,14 +3,12 @@ module Gitlab module Status module Pipeline class Factory < Status::Factory - private - - def extended_statuses + def self.extended_statuses [Pipeline::SuccessWithWarnings] end - def core_status - super.extend(Status::Pipeline::Common) + def self.common_helpers + Status::Pipeline::Common end end end diff --git a/lib/gitlab/ci/status/stage/factory.rb b/lib/gitlab/ci/status/stage/factory.rb index c6522d5ada1..689a5dd45bc 100644 --- a/lib/gitlab/ci/status/stage/factory.rb +++ b/lib/gitlab/ci/status/stage/factory.rb @@ -3,10 +3,8 @@ module Gitlab module Status module Stage class Factory < Status::Factory - private - - def core_status - super.extend(Status::Stage::Common) + def self.common_helpers + Status::Stage::Common end end end -- cgit v1.2.1 From 5059d0b834eeea22ada4b6ac98cfddc2123691e9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 8 Dec 2016 14:28:49 +0100 Subject: Incorporate permission checks into new CI statuses [ci skip] --- lib/gitlab/ci/status/build/cancelable.rb | 12 +++++++----- lib/gitlab/ci/status/build/common.rb | 10 +++++----- lib/gitlab/ci/status/build/play.rb | 12 +++++++----- lib/gitlab/ci/status/build/retryable.rb | 12 +++++++----- lib/gitlab/ci/status/build/stop.rb | 12 +++++++----- lib/gitlab/ci/status/core.rb | 13 ++++++------- lib/gitlab/ci/status/extended.rb | 8 ++++++-- lib/gitlab/ci/status/factory.rb | 4 ++-- lib/gitlab/ci/status/pipeline/common.rb | 10 +++++----- lib/gitlab/ci/status/pipeline/success_with_warnings.rb | 4 ++-- lib/gitlab/ci/status/stage/common.rb | 12 ++++++------ 11 files changed, 60 insertions(+), 49 deletions(-) diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb index bff0464ef0c..a8830b04715 100644 --- a/lib/gitlab/ci/status/build/cancelable.rb +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -3,10 +3,10 @@ module Gitlab module Status module Status class Cancelable < SimpleDelegator - extend Status::Extended + include Status::Extended - def has_action?(current_user) - can?(current_user, :update_build, subject) + def has_action? + can?(user, :update_build, subject) end def action_icon @@ -14,14 +14,16 @@ module Gitlab end def action_path - cancel_namespace_project_build_path(subject.project.namespace, subject.project, subject) + cancel_namespace_project_build_path(subject.project.namespace, + subject.project, + subject) end def action_method :post end - def self.matches?(build) + def self.matches?(build, user) build.cancelable? end end diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb index 2fb79afa3d3..2b602f1e247 100644 --- a/lib/gitlab/ci/status/build/common.rb +++ b/lib/gitlab/ci/status/build/common.rb @@ -3,14 +3,14 @@ module Gitlab module Status module Build module Common - def has_details?(current_user) - can?(current_user, :read_build, subject) + def has_details? + can?(user, :read_build, subject) end def details_path - namespace_project_build_path(@subject.project.namespace, - @subject.project, - @subject.pipeline) + namespace_project_build_path(subject.project.namespace, + subject.project, + subject.pipeline) end end end diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index d295850137b..70c08197ea1 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -3,7 +3,7 @@ module Gitlab module Status module Status class Play < SimpleDelegator - extend Status::Extended + include Status::Extended def text 'play' @@ -13,8 +13,8 @@ module Gitlab 'play' end - def has_action?(current_user) - can?(current_user, :update_build, subject) + def has_action? + can?(user, :update_build, subject) end def action_icon @@ -22,14 +22,16 @@ module Gitlab end def action_path - play_namespace_project_build_path(subject.project.namespace, subject.project, subject) + play_namespace_project_build_path(subject.project.namespace, + subject.project, + subject) end def action_method :post end - def self.matches?(build) + def self.matches?(build, user) build.playable? && !build.stops_environment? end end diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb index b3c0eedadf8..3309e8808e1 100644 --- a/lib/gitlab/ci/status/build/retryable.rb +++ b/lib/gitlab/ci/status/build/retryable.rb @@ -3,10 +3,10 @@ module Gitlab module Status module Status class Retryable < SimpleDelegator - extend Status::Extended + include Status::Extended - def has_action?(current_user) - can?(current_user, :update_build, subject) + def has_action? + can?(user, :update_build, subject) end def action_icon @@ -14,14 +14,16 @@ module Gitlab end def action_path - retry_namespace_project_build_path(subject.project.namespace, subject.project, subject) + retry_namespace_project_build_path(subject.project.namespace, + subject.project, + subject) end def action_method :post end - def self.matches?(build) + def self.matches?(build, user) build.retryable? end end diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index 261de9695c5..6fb51890bec 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -3,7 +3,7 @@ module Gitlab module Status module Status class Play < SimpleDelegator - extend Status::Extended + include Status::Extended def text 'stop' @@ -21,8 +21,8 @@ module Gitlab 'stop' end - def has_action?(current_user) - can?(current_user, :update_build, subject) + def has_action? + can?(user, :update_build, subject) end def action_icon @@ -30,14 +30,16 @@ module Gitlab end def action_path - play_namespace_project_build_path(subject.project.namespace, subject.project, subject) + play_namespace_project_build_path(subject.project.namespace, + subject.project, + subject) end def action_method :post end - def self.matches?(build) + def self.matches?(build, user) build.playable? && build.stops_environment? end end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 6b47096f811..df371363736 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -6,8 +6,11 @@ module Gitlab class Core include Gitlab::Routing.url_helpers - def initialize(subject) + attr_reader :subject, :user + + def initialize(subject, user) @subject = subject + @user = user end def icon @@ -18,10 +21,6 @@ module Gitlab raise NotImplementedError end - def title - "#{@subject.class.name.demodulize}: #{label}" - end - # Deprecation warning: this method is here because we need to maintain # backwards compatibility with legacy statuses. We often do something # like "ci-status ci-status-#{status}" to set CSS class. @@ -33,7 +32,7 @@ module Gitlab self.class.name.demodulize.downcase.underscore end - def has_details?(_user = nil) + def has_details? false end @@ -41,7 +40,7 @@ module Gitlab raise NotImplementedError end - def has_action?(_user = nil) + def has_action? false end diff --git a/lib/gitlab/ci/status/extended.rb b/lib/gitlab/ci/status/extended.rb index 93e6eff1c94..d367c9bda69 100644 --- a/lib/gitlab/ci/status/extended.rb +++ b/lib/gitlab/ci/status/extended.rb @@ -2,8 +2,12 @@ module Gitlab module Ci module Status module Extended - def matches?(_subject, _user) - raise NotImplementedError + extend ActiveSupport::Concern + + class_methods do + def matches?(_subject, _user) + raise NotImplementedError + end end end end diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb index 944e0fdde2d..ae9ef895df4 100644 --- a/lib/gitlab/ci/status/factory.rb +++ b/lib/gitlab/ci/status/factory.rb @@ -2,7 +2,7 @@ module Gitlab module Ci module Status class Factory - def initialize(subject, user = nil) + def initialize(subject, user) @subject = subject @user = user end @@ -32,7 +32,7 @@ module Gitlab def core_status Gitlab::Ci::Status .const_get(simple_status.capitalize) - .new(@subject) + .new(@subject, @user) .extend(self.class.common_helpers) end diff --git a/lib/gitlab/ci/status/pipeline/common.rb b/lib/gitlab/ci/status/pipeline/common.rb index 5f79044a496..76bfd18bf40 100644 --- a/lib/gitlab/ci/status/pipeline/common.rb +++ b/lib/gitlab/ci/status/pipeline/common.rb @@ -3,14 +3,14 @@ module Gitlab module Status module Pipeline module Common - def has_details?(current_user) - can?(current_user, :read_pipeline, subject) + def has_details? + can?(user, :read_pipeline, subject) end def details_path - namespace_project_pipeline_path(@subject.project.namespace, - @subject.project, - @subject) + namespace_project_pipeline_path(subject.project.namespace, + subject.project, + subject) end def has_action? diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb index 4b040d60df8..a7c98f9e909 100644 --- a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb +++ b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb @@ -3,7 +3,7 @@ module Gitlab module Status module Pipeline class SuccessWithWarnings < SimpleDelegator - extend Status::Extended + include Status::Extended def text 'passed' @@ -21,7 +21,7 @@ module Gitlab 'success_with_warnings' end - def self.matches?(pipeline) + def self.matches?(pipeline, user) pipeline.success? && pipeline.has_warnings? end end diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb index e6ee2f92341..6851ceda317 100644 --- a/lib/gitlab/ci/status/stage/common.rb +++ b/lib/gitlab/ci/status/stage/common.rb @@ -3,15 +3,15 @@ module Gitlab module Status module Stage module Common - def has_details?(current_user) - can?(current_user, :read_pipeline, subject) + def has_details? + can?(user, :read_pipeline, subject) end def details_path - namespace_project_pipeline_path(@subject.project.namespace, - @subject.project, - @subject.pipeline, - anchor: @subject.name) + namespace_project_pipeline_path(subject.project.namespace, + subject.project, + subject.pipeline, + anchor: subject.name) end def has_action? -- cgit v1.2.1 From 23feb6a773a49123c3ece0ff2ed675fd294d8817 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 8 Dec 2016 14:40:21 +0100 Subject: Fix tests related to detailed statuses and permissions [ci skip] --- spec/lib/gitlab/ci/status/canceled_spec.rb | 4 +++- spec/lib/gitlab/ci/status/created_spec.rb | 8 +++----- spec/lib/gitlab/ci/status/extended_spec.rb | 2 +- spec/lib/gitlab/ci/status/factory_spec.rb | 6 ++++-- spec/lib/gitlab/ci/status/failed_spec.rb | 8 +++----- spec/lib/gitlab/ci/status/pending_spec.rb | 8 +++----- spec/lib/gitlab/ci/status/pipeline/common_spec.rb | 4 +++- spec/lib/gitlab/ci/status/pipeline/factory_spec.rb | 4 +++- spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb | 2 +- spec/lib/gitlab/ci/status/running_spec.rb | 8 +++----- spec/lib/gitlab/ci/status/skipped_spec.rb | 8 +++----- spec/lib/gitlab/ci/status/stage/common_spec.rb | 8 ++++++-- spec/lib/gitlab/ci/status/stage/factory_spec.rb | 8 ++++++-- spec/lib/gitlab/ci/status/success_spec.rb | 8 +++----- 14 files changed, 45 insertions(+), 41 deletions(-) diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 619ecbcba67..eaf974bb953 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Canceled do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'canceled' } diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index 157302c65a8..2ce176a29d6 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Created do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'created' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Created do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_created' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: created' } - end end diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb index 120e121aae5..864121dec4b 100644 --- a/spec/lib/gitlab/ci/status/extended_spec.rb +++ b/spec/lib/gitlab/ci/status/extended_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Ci::Status::Extended do end it 'requires subclass to implement matcher' do - expect { subject.matches?(double) } + expect { subject.matches?(double, double) } .to raise_error(NotImplementedError) end end diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb index d5bd7f7102b..f92a1c149bf 100644 --- a/spec/lib/gitlab/ci/status/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/factory_spec.rb @@ -2,15 +2,17 @@ require 'spec_helper' describe Gitlab::Ci::Status::Factory do subject do - described_class.new(object) + described_class.new(resource, user) end + let(:user) { create(:user) } + let(:status) { subject.fabricate! } context 'when object has a core status' do HasStatus::AVAILABLE_STATUSES.each do |core_status| context "when core status is #{core_status}" do - let(:object) { double(status: core_status) } + let(:resource) { double(status: core_status) } it "fabricates a core status #{core_status}" do expect(status).to be_a( diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index 0b3cb8168e6..9d527e6a7ef 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Failed do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'failed' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Failed do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_failed' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: failed' } - end end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index 57c901c1202..d03f595d3c7 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pending do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'pending' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Pending do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_pending' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: pending' } - end end diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb index 21adee3f8e7..4f32ae5d809 100644 --- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -1,11 +1,13 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Common do + let(:user) { create(:user) } let(:pipeline) { create(:ci_pipeline) } subject do Class.new(Gitlab::Ci::Status::Core) - .new(pipeline).extend(described_class) + .new(pipeline, user) + .extend(described_class) end it 'does not have action' do diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index d6243940f2e..c6b2582652d 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -1,8 +1,10 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Factory do + let(:user) { create(:user) } + subject do - described_class.new(pipeline) + described_class.new(pipeline, user) end let(:status) do diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb index 02e526e3de2..634f80088d5 100644 --- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do subject do - described_class.new(double('status')) + described_class.new(double('status'), double('user')) end describe '#test' do diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index c023f1872cc..9f47090d396 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Running do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'running' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Running do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_running' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: running' } - end end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index d4f7f4b3b70..94601648a8d 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Skipped do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'skipped' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Skipped do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_skipped' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: skipped' } - end end diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb index f3259c6f23e..9b7e6777dc1 100644 --- a/spec/lib/gitlab/ci/status/stage/common_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb @@ -1,12 +1,16 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Common do + let(:user) { create(:user) } let(:pipeline) { create(:ci_empty_pipeline) } - let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } + + let(:stage) do + build(:ci_stage, pipeline: pipeline, name: 'test') + end subject do Class.new(Gitlab::Ci::Status::Core) - .new(stage).extend(described_class) + .new(stage, user).extend(described_class) end it 'does not have action' do diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 17929665c83..5a281564415 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -1,11 +1,15 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Factory do + let(:user) { create(:user) } let(:pipeline) { create(:ci_empty_pipeline) } - let(:stage) { build(:ci_stage, pipeline: pipeline, name: 'test') } + + let(:stage) do + build(:ci_stage, pipeline: pipeline, name: 'test') + end subject do - described_class.new(stage) + described_class.new(stage, user) end let(:status) do diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index 9e261a3aa5f..90f9f615e0d 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Status::Success do - subject { described_class.new(double('subject')) } + subject do + described_class.new(double('subject'), double('user')) + end describe '#text' do it { expect(subject.label).to eq 'passed' } @@ -14,8 +16,4 @@ describe Gitlab::Ci::Status::Success do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_success' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: passed' } - end end -- cgit v1.2.1 From f0cd73bfadbe9fa27b25473dab61d8c566292392 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 8 Dec 2016 14:51:38 +0100 Subject: Fix some detailed statuses specs related to abilities --- app/models/ability.rb | 6 ++++++ lib/gitlab/ci/status/core.rb | 1 + lib/gitlab/ci/status/stage/common.rb | 2 +- spec/lib/gitlab/ci/status/canceled_spec.rb | 4 ---- spec/lib/gitlab/ci/status/extended_spec.rb | 2 +- spec/lib/gitlab/ci/status/pipeline/common_spec.rb | 7 ++++++- spec/lib/gitlab/ci/status/pipeline/factory_spec.rb | 5 +++++ .../status/pipeline/success_with_warnings_spec.rb | 10 +++++----- spec/lib/gitlab/ci/status/stage/common_spec.rb | 23 +++++++++++++++++----- spec/lib/gitlab/ci/status/stage/factory_spec.rb | 7 ++++++- 10 files changed, 49 insertions(+), 18 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index fa8f8bc3a5f..ce461caf686 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,4 +1,10 @@ class Ability + module Allowable + def can?(user, action, subject) + Ability.allowed?(user, action, subject) + end + end + class << self # Given a list of users and a project this method returns the users that can # read the given project. diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index df371363736..7e9f6e35012 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -5,6 +5,7 @@ module Gitlab # class Core include Gitlab::Routing.url_helpers + include Ability::Allowable attr_reader :subject, :user diff --git a/lib/gitlab/ci/status/stage/common.rb b/lib/gitlab/ci/status/stage/common.rb index 6851ceda317..7852f492e1d 100644 --- a/lib/gitlab/ci/status/stage/common.rb +++ b/lib/gitlab/ci/status/stage/common.rb @@ -4,7 +4,7 @@ module Gitlab module Stage module Common def has_details? - can?(user, :read_pipeline, subject) + can?(user, :read_pipeline, subject.pipeline) end def details_path diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index eaf974bb953..4639278ad45 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -16,8 +16,4 @@ describe Gitlab::Ci::Status::Canceled do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_canceled' } end - - describe '#title' do - it { expect(subject.title).to eq 'Double: canceled' } - end end diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb index 864121dec4b..c2d74ca5cde 100644 --- a/spec/lib/gitlab/ci/status/extended_spec.rb +++ b/spec/lib/gitlab/ci/status/extended_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Extended do subject do - Class.new.extend(described_class) + Class.new.include(described_class) end it 'requires subclass to implement matcher' do diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb index 4f32ae5d809..2df9d574677 100644 --- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Common do let(:user) { create(:user) } - let(:pipeline) { create(:ci_pipeline) } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_pipeline, project: project) } subject do Class.new(Gitlab::Ci::Status::Core) @@ -10,6 +11,10 @@ describe Gitlab::Ci::Status::Pipeline::Common do .extend(described_class) end + before do + project.team << [user, :developer] + end + it 'does not have action' do expect(subject).not_to have_action end diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index c6b2582652d..d4a2dc7fcc1 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Factory do let(:user) { create(:user) } + let(:project) { pipeline.project } subject do described_class.new(pipeline, user) @@ -11,6 +12,10 @@ describe Gitlab::Ci::Status::Pipeline::Factory do subject.fabricate! end + before do + project.team << [user, :developer] + end + context 'when pipeline has a core status' do HasStatus::AVAILABLE_STATUSES.each do |core_status| context "when core status is #{core_status}" do diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb index 634f80088d5..7e3383c307f 100644 --- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do subject do - described_class.new(double('status'), double('user')) + described_class.new(double('status')) end describe '#test' do @@ -29,13 +29,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do end it 'is a correct match' do - expect(described_class.matches?(pipeline)).to eq true + expect(described_class.matches?(pipeline, double)).to eq true end end context 'when pipeline does not have warnings' do it 'does not match' do - expect(described_class.matches?(pipeline)).to eq false + expect(described_class.matches?(pipeline, double)).to eq false end end end @@ -51,13 +51,13 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do end it 'does not match' do - expect(described_class.matches?(pipeline)).to eq false + expect(described_class.matches?(pipeline, double)).to eq false end end context 'when pipeline does not have warnings' do it 'does not match' do - expect(described_class.matches?(pipeline)).to eq false + expect(described_class.matches?(pipeline, double)).to eq false end end end diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb index 9b7e6777dc1..8814a7614a0 100644 --- a/spec/lib/gitlab/ci/status/stage/common_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Common do let(:user) { create(:user) } - let(:pipeline) { create(:ci_empty_pipeline) } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:stage) do build(:ci_stage, pipeline: pipeline, name: 'test') @@ -17,14 +18,26 @@ describe Gitlab::Ci::Status::Stage::Common do expect(subject).not_to have_action end - it 'has details' do - expect(subject).to have_details - end - it 'links to the pipeline details page' do expect(subject.details_path) .to include "pipelines/#{pipeline.id}" expect(subject.details_path) .to include "##{stage.name}" end + + context 'when user has permission to read pipeline' do + before do + project.team << [user, :master] + end + + it 'has details' do + expect(subject).to have_details + end + end + + context 'when user does not have permission to read pipeline' do + it 'does not have details' do + expect(subject).not_to have_details + end + end end diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 5a281564415..6f8721d30c2 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe Gitlab::Ci::Status::Stage::Factory do let(:user) { create(:user) } - let(:pipeline) { create(:ci_empty_pipeline) } + let(:project) { create(:empty_project) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:stage) do build(:ci_stage, pipeline: pipeline, name: 'test') @@ -16,6 +17,10 @@ describe Gitlab::Ci::Status::Stage::Factory do subject.fabricate! end + before do + project.team << [user, :developer] + end + context 'when stage has a core status' do HasStatus::AVAILABLE_STATUSES.each do |core_status| context "when core status is #{core_status}" do -- cgit v1.2.1 From 980009e6e85562c9ee8026878929d09905b2a0a9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 17:52:24 +0100 Subject: Fix auto loading of constants for Ci Statuses --- app/models/ci/build.rb | 6 +++--- app/models/ci/pipeline.rb | 4 ++-- app/models/ci/stage.rb | 4 ++-- app/models/commit_status.rb | 4 ++-- app/views/ci/status/_icon_with_label.html.haml | 13 +++++++------ lib/gitlab/ci/status/build/cancelable.rb | 2 +- lib/gitlab/ci/status/build/play.rb | 2 +- lib/gitlab/ci/status/build/retryable.rb | 2 +- lib/gitlab/ci/status/build/stop.rb | 4 ++-- 9 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 73564dd2aa0..65ee327a8e5 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -100,8 +100,8 @@ module Ci end end - def detailed_status - Gitlab::Ci::Status::Build::Factory.new(self).fabricate! + def detailed_status(current_user) + Gitlab::Ci::Status::Build::Factory.new(self, current_user).fabricate! end def manual? @@ -156,7 +156,7 @@ module Ci end def environment_action - self.options.fetch(:environment, {}).fetch(:action, 'start') + self.options.fetch(:environment, {}).fetch(:action, 'start') if self.options end def outdated_deployment? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index fda8228a1e9..1f33106d358 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -336,8 +336,8 @@ module Ci .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } end - def detailed_status - Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate! + def detailed_status(current_user) + Gitlab::Ci::Status::Pipeline::Factory.new(self, current_user).fabricate! end private diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index d2a37c0a827..be52cce20f1 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -22,8 +22,8 @@ module Ci @status ||= statuses.latest.status end - def detailed_status - Gitlab::Ci::Status::Stage::Factory.new(self).fabricate! + def detailed_status(current_user) + Gitlab::Ci::Status::Stage::Factory.new(self, current_user).fabricate! end def statuses diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index fce16174e22..6548a7dda2c 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -132,7 +132,7 @@ class CommitStatus < ActiveRecord::Base false end - def detailed_status - Gitlab::Ci::Status::Factory.new(self).fabricate! + def detailed_status(current_user) + Gitlab::Ci::Status::Factory.new(self, current_user).fabricate! end end diff --git a/app/views/ci/status/_icon_with_label.html.haml b/app/views/ci/status/_icon_with_label.html.haml index 65a74e88444..d3fe332cc78 100644 --- a/app/views/ci/status/_icon_with_label.html.haml +++ b/app/views/ci/status/_icon_with_label.html.haml @@ -1,10 +1,11 @@ -- details_path = subject.details_path if subject.has_details?(current_user) -- klass = "ci-status ci-#{subject.status}" +- detailed_status = subject.detailed_status(current_user) +- details_path = detailed_status.details_path if detailed_status.has_details? +- klass = "ci-status ci-#{detailed_status}" - if details_path = link_to details_path, class: klass do - = custom_icon(status.icon) - = status.text + = custom_icon(detailed_status.icon) + = detailed_status.text - else %span{ class: klass } - = custom_icon(status.icon) - = status.text + = custom_icon(detailed_status.icon) + = detailed_status.text diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb index a8830b04715..88be0cd924b 100644 --- a/lib/gitlab/ci/status/build/cancelable.rb +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -1,7 +1,7 @@ module Gitlab module Ci module Status - module Status + module Build class Cancelable < SimpleDelegator include Status::Extended diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index 70c08197ea1..57c7058fe84 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -1,7 +1,7 @@ module Gitlab module Ci module Status - module Status + module Build class Play < SimpleDelegator include Status::Extended diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb index 3309e8808e1..69f2ad1d277 100644 --- a/lib/gitlab/ci/status/build/retryable.rb +++ b/lib/gitlab/ci/status/build/retryable.rb @@ -1,7 +1,7 @@ module Gitlab module Ci module Status - module Status + module Build class Retryable < SimpleDelegator include Status::Extended diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index 6fb51890bec..cd9bd959a7c 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -1,8 +1,8 @@ module Gitlab module Ci module Status - module Status - class Play < SimpleDelegator + module Build + class Stop < SimpleDelegator include Status::Extended def text -- cgit v1.2.1 From d1dd89356c4cd5e70f2b81ef416e52b745486293 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 18:16:23 +0100 Subject: Rename icon_with_label to icon_with_description --- app/views/admin/runners/show.html.haml | 2 +- app/views/ci/status/_icon_with_description.html.haml | 12 ++++++++++++ app/views/ci/status/_icon_with_label.html.haml | 11 ----------- app/views/projects/builds/_header.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 2 +- app/views/projects/ci/pipelines/_pipeline.html.haml | 2 +- .../generic_commit_statuses/_generic_commit_status.html.haml | 2 +- app/views/projects/pipelines/_info.html.haml | 2 +- app/views/projects/stage/_graph.html.haml | 4 ++-- 9 files changed, 20 insertions(+), 19 deletions(-) create mode 100644 app/views/ci/status/_icon_with_description.html.haml delete mode 100644 app/views/ci/status/_icon_with_label.html.haml diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index fa8be25ffa8..badeb11b208 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -91,7 +91,7 @@ %strong ##{build.id} %td.status - = render "ci/status/icon_with_label", subject: build + = render "ci/status/icon_with_description", subject: build %td.status - if project diff --git a/app/views/ci/status/_icon_with_description.html.haml b/app/views/ci/status/_icon_with_description.html.haml new file mode 100644 index 00000000000..34c923440d0 --- /dev/null +++ b/app/views/ci/status/_icon_with_description.html.haml @@ -0,0 +1,12 @@ +- detailed_status = subject.detailed_status(current_user) +- details_path = detailed_status.details_path if detailed_status.has_details? +- klass = "ci-status ci-#{detailed_status}" + +- if details_path + = link_to details_path, class: klass do + = custom_icon(detailed_status.icon) + = detailed_status.text +- else + %span{ class: klass } + = custom_icon(detailed_status.icon) + = detailed_status.text diff --git a/app/views/ci/status/_icon_with_label.html.haml b/app/views/ci/status/_icon_with_label.html.haml deleted file mode 100644 index d3fe332cc78..00000000000 --- a/app/views/ci/status/_icon_with_label.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -- detailed_status = subject.detailed_status(current_user) -- details_path = detailed_status.details_path if detailed_status.has_details? -- klass = "ci-status ci-#{detailed_status}" -- if details_path - = link_to details_path, class: klass do - = custom_icon(detailed_status.icon) - = detailed_status.text -- else - %span{ class: klass } - = custom_icon(detailed_status.icon) - = detailed_status.text diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index 5e4e30f08d5..85d1793ecb9 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,6 +1,6 @@ .content-block.build-header .header-content - = render "ci/status/icon_with_label", subject: build + = render "ci/status/icon_with_description", subject: build Build %strong ##{@build.id} in pipeline diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 6b0cd3e49a0..4257bb86859 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -9,7 +9,7 @@ %tr.build.commit{class: ('retried' if retried)} %td.status - = render "ci/status/icon_with_label", subject: build + = render "ci/status/icon_with_description", subject: build %td.branch-commit - if can?(current_user, :read_build, build) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 84243e4306d..6dff955ea3d 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -4,7 +4,7 @@ %tr.commit %td.commit-link - = render "ci/status/icon_with_label", subject: pipeline + = render "ci/status/icon_with_description", subject: pipeline %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 69cb1631ee8..1dd07ae1a2a 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -8,7 +8,7 @@ %tr.generic_commit_status{class: ('retried' if retried)} %td.status - = render "ci/status/icon_with_label", subject: generic_commit_status + = render "ci/status/icon_with_description", subject: generic_commit_status %td.generic_commit_status-link - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index f7385184a2b..d05697b4ee3 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,6 +1,6 @@ .page-content-header .header-main-content - = render "ci/status/icon_with_label", subject: @pipeline + = render "ci/status/icon_with_description", subject: @pipeline %strong Pipeline ##{@commit.pipelines.last.id} triggered #{time_ago_with_tooltip(@commit.authored_date)} by = author_avatar(@commit, size: 24) diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml index 1d8fa10db0c..745b6d143f4 100644 --- a/app/views/projects/stage/_graph.html.haml +++ b/app/views/projects/stage/_graph.html.haml @@ -14,9 +14,9 @@ %li.build{ class: ("playable" if is_playable) } .curve .build-content - = render "projects/#{status.to_partial_path}_pipeline", subject: status + = render 'ci/status/icon_with_name_and_action', subject: status - else %li.build .curve .dropdown.inline.build-content - = render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses + = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses -- cgit v1.2.1 From d9a979c84f2d91b25ccde9131f9b4b03b40c4ef7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 18:18:30 +0100 Subject: Add action_class/action_title --- lib/gitlab/ci/status/build/cancelable.rb | 6 +++++- lib/gitlab/ci/status/build/play.rb | 8 ++++++++ lib/gitlab/ci/status/build/retryable.rb | 6 +++++- lib/gitlab/ci/status/build/stop.rb | 6 +++++- lib/gitlab/ci/status/core.rb | 7 +++++++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb index 88be0cd924b..a979fe7d573 100644 --- a/lib/gitlab/ci/status/build/cancelable.rb +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -10,7 +10,7 @@ module Gitlab end def action_icon - 'remove' + 'ban' end def action_path @@ -23,6 +23,10 @@ module Gitlab :post end + def action_title + 'Cancel' + end + def self.matches?(build, user) build.cancelable? end diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index 57c7058fe84..e3066d40a37 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -17,10 +17,18 @@ module Gitlab can?(user, :update_build, subject) end + def action_title + 'Play' + end + def action_icon 'play' end + def action_class + 'ci-play-icon' + end + def action_path play_namespace_project_build_path(subject.project.namespace, subject.project, diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb index 69f2ad1d277..8e38d6a8523 100644 --- a/lib/gitlab/ci/status/build/retryable.rb +++ b/lib/gitlab/ci/status/build/retryable.rb @@ -10,7 +10,11 @@ module Gitlab end def action_icon - 'repeat' + 'refresh' + end + + def action_title + 'Retry' end def action_path diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index cd9bd959a7c..487fd033960 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -26,7 +26,11 @@ module Gitlab end def action_icon - :play + 'stop' + end + + def action_title + 'Stop' end def action_path diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 7e9f6e35012..dd3a824e486 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -49,6 +49,9 @@ module Gitlab raise NotImplementedError end + def action_class + end + def action_path raise NotImplementedError end @@ -56,6 +59,10 @@ module Gitlab def action_method raise NotImplementedError end + + def action_title + raise NotImplementedError + end end end end -- cgit v1.2.1 From 23f02681c036b150966ce4459410c94694167b34 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 8 Dec 2016 18:21:11 +0100 Subject: Revert some unneeded changes --- app/views/projects/stage/_graph.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml index 745b6d143f4..bf8c75b6e5c 100644 --- a/app/views/projects/stage/_graph.html.haml +++ b/app/views/projects/stage/_graph.html.haml @@ -14,7 +14,7 @@ %li.build{ class: ("playable" if is_playable) } .curve .build-content - = render 'ci/status/icon_with_name_and_action', subject: status + = render "projects/#{status.to_partial_path}_pipeline", subject: status - else %li.build .curve -- cgit v1.2.1 From dc67554c08ac6cfe23809af9f086336ca9ca740e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 13:28:02 +0100 Subject: Improve detailed status badge partial --- app/views/admin/runners/show.html.haml | 2 +- app/views/ci/status/_badge.html.haml | 8 ++++++++ app/views/ci/status/_icon_with_description.html.haml | 12 ------------ app/views/projects/builds/_header.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 2 +- app/views/projects/ci/pipelines/_pipeline.html.haml | 2 +- .../generic_commit_statuses/_generic_commit_status.html.haml | 2 +- app/views/projects/pipelines/_info.html.haml | 2 +- 8 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 app/views/ci/status/_badge.html.haml delete mode 100644 app/views/ci/status/_icon_with_description.html.haml diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index badeb11b208..ca503e35623 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -91,7 +91,7 @@ %strong ##{build.id} %td.status - = render "ci/status/icon_with_description", subject: build + = render 'ci/status/badge', status: build.detailed_status(current_user) %td.status - if project diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml new file mode 100644 index 00000000000..b1b6e9c2b05 --- /dev/null +++ b/app/views/ci/status/_badge.html.haml @@ -0,0 +1,8 @@ +- if status.has_details? + = link_to status.details_path, class: "ci-status ci-#{status}" do + = custom_icon(status.icon) + = status.text +- else + %span{ class: "ci-status ci-#{status}" } + = custom_icon(status.icon) + = detailed_status.text diff --git a/app/views/ci/status/_icon_with_description.html.haml b/app/views/ci/status/_icon_with_description.html.haml deleted file mode 100644 index 34c923440d0..00000000000 --- a/app/views/ci/status/_icon_with_description.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -- detailed_status = subject.detailed_status(current_user) -- details_path = detailed_status.details_path if detailed_status.has_details? -- klass = "ci-status ci-#{detailed_status}" - -- if details_path - = link_to details_path, class: klass do - = custom_icon(detailed_status.icon) - = detailed_status.text -- else - %span{ class: klass } - = custom_icon(detailed_status.icon) - = detailed_status.text diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index 85d1793ecb9..057a720a54a 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,6 +1,6 @@ .content-block.build-header .header-content - = render "ci/status/icon_with_description", subject: build + = render 'ci/status/badge', status: @build.detailed_status(current_user) Build %strong ##{@build.id} in pipeline diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 4257bb86859..f1cb0201032 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -9,7 +9,7 @@ %tr.build.commit{class: ('retried' if retried)} %td.status - = render "ci/status/icon_with_description", subject: build + = render "ci/status/badge", status: build.detailed_status(current_user) %td.branch-commit - if can?(current_user, :read_build, build) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 6dff955ea3d..3f05a21990f 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -4,7 +4,7 @@ %tr.commit %td.commit-link - = render "ci/status/icon_with_description", subject: pipeline + = render 'ci/status/badge', status: pipeline.detailed_status(current_user) %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 1dd07ae1a2a..9f444f076c0 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -8,7 +8,7 @@ %tr.generic_commit_status{class: ('retried' if retried)} %td.status - = render "ci/status/icon_with_description", subject: generic_commit_status + = render 'ci/status/badge', status: generic_commit_status.detailed_status(current_user) %td.generic_commit_status-link - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index d05697b4ee3..b00ba2d5307 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,6 +1,6 @@ .page-content-header .header-main-content - = render "ci/status/icon_with_description", subject: @pipeline + = render 'ci/status/badge', status: @pipeline.detailed_status(current_user) %strong Pipeline ##{@commit.pipelines.last.id} triggered #{time_ago_with_tooltip(@commit.authored_date)} by = author_avatar(@commit, size: 24) -- cgit v1.2.1 From d60820146f87863a1ef93ccdc678fd617029950a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 13:28:20 +0100 Subject: Fix path to build status details in common helpers --- lib/gitlab/ci/status/build/common.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/status/build/common.rb b/lib/gitlab/ci/status/build/common.rb index 2b602f1e247..3fec2c5d4db 100644 --- a/lib/gitlab/ci/status/build/common.rb +++ b/lib/gitlab/ci/status/build/common.rb @@ -10,7 +10,7 @@ module Gitlab def details_path namespace_project_build_path(subject.project.namespace, subject.project, - subject.pipeline) + subject) end end end -- cgit v1.2.1 From 7c9a85e35386623bc26374e4eb9b4a53e4fbbce7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 9 Dec 2016 14:37:41 +0000 Subject: Fix TypeError: Cannot read property 'initTabs' --- app/assets/javascripts/pipelines.js.es6 | 2 +- changelogs/unreleased/25483-broken-tabs.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/25483-broken-tabs.yml diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index 72c6c4a1fcd..a7a384fd856 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -4,7 +4,7 @@ ((global) => { class Pipelines { - constructor(options) { + constructor(options = {}) { if (options.initTabs && options.tabsOptions) { new global.LinkedTabs(options.tabsOptions); diff --git a/changelogs/unreleased/25483-broken-tabs.yml b/changelogs/unreleased/25483-broken-tabs.yml new file mode 100644 index 00000000000..7bc50bdf860 --- /dev/null +++ b/changelogs/unreleased/25483-broken-tabs.yml @@ -0,0 +1,4 @@ +--- +title: Fix TypeError: Cannot read property 'initTabs' on commit builds tab +merge_request: +author: -- cgit v1.2.1 From 401a2ec0b159b3c5f4de617768b9a0489a7cdde3 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 9 Dec 2016 15:13:20 +0000 Subject: Adds tests to prevent future errors. Fix undefined variable in es5 --- app/assets/javascripts/pipelines.js.es6 | 2 +- changelogs/unreleased/25483-broken-tabs.yml | 2 +- spec/javascripts/fixtures/pipeline_graph.html.haml | 15 +++++++++++++ spec/javascripts/pipelines_spec.js.es6 | 25 ++++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 spec/javascripts/fixtures/pipeline_graph.html.haml create mode 100644 spec/javascripts/pipelines_spec.js.es6 diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index a7a384fd856..fd1e320dc35 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -16,7 +16,7 @@ addMarginToBuildColumns() { this.pipelineGraph = document.querySelector('.pipeline-graph'); const secondChildBuildNodes = document.querySelector('.pipeline-graph').querySelectorAll('.build:nth-child(2)'); - for (buildNodeIndex in secondChildBuildNodes) { + for (const buildNodeIndex in secondChildBuildNodes) { const buildNode = secondChildBuildNodes[buildNodeIndex]; const firstChildBuildNode = buildNode.previousElementSibling; if (!firstChildBuildNode || !firstChildBuildNode.matches('.build')) continue; diff --git a/changelogs/unreleased/25483-broken-tabs.yml b/changelogs/unreleased/25483-broken-tabs.yml index 7bc50bdf860..d6c92014bea 100644 --- a/changelogs/unreleased/25483-broken-tabs.yml +++ b/changelogs/unreleased/25483-broken-tabs.yml @@ -1,4 +1,4 @@ --- title: Fix TypeError: Cannot read property 'initTabs' on commit builds tab -merge_request: +merge_request: 8009 author: diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml new file mode 100644 index 00000000000..be5f105767c --- /dev/null +++ b/spec/javascripts/fixtures/pipeline_graph.html.haml @@ -0,0 +1,15 @@ +%div.pipeline-visualization.pipeline-graph + %ul.stage-column-list + %li.stage-column + .stage-name + %a{:href => "/"} + Test + .builds-container + %ul + %li.build + .curve + .build-content + %a + %svg + .ci-status-text + stop_review diff --git a/spec/javascripts/pipelines_spec.js.es6 b/spec/javascripts/pipelines_spec.js.es6 new file mode 100644 index 00000000000..85c9cf4b4f1 --- /dev/null +++ b/spec/javascripts/pipelines_spec.js.es6 @@ -0,0 +1,25 @@ +//= require pipelines + +(() => { + describe('Pipelines', () => { + fixture.preload('pipeline_graph'); + + beforeEach(() => { + fixture.load('pipeline_graph'); + }); + + it('should be defined', () => { + expect(window.gl.Pipelines).toBeDefined(); + }); + + it('should create a `Pipelines` instance without options', () => { + expect(() => { new window.gl.Pipelines(); }).not.toThrow(); //eslint-disable-line + }); + + it('should create a `Pipelines` instance with options', () => { + const pipelines = new window.gl.Pipelines({ foo: 'bar' }); + + expect(pipelines.pipelineGraph).toBeDefined(); + }); + }); +})(); -- cgit v1.2.1 From 94e0f402334af845bb44e92a3e2646780d633ce2 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 9 Dec 2016 16:14:54 +0000 Subject: Fix Pipeline graph disappeared from the builds tab in commits and merge request views --- app/assets/javascripts/pipelines.js.es6 | 4 ++-- app/views/projects/commit/_pipeline.html.haml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index fd1e320dc35..f09c6bb7def 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -14,8 +14,8 @@ } addMarginToBuildColumns() { - this.pipelineGraph = document.querySelector('.pipeline-graph'); - const secondChildBuildNodes = document.querySelector('.pipeline-graph').querySelectorAll('.build:nth-child(2)'); + this.pipelineGraph = document.querySelector('.js-pipeline-graph'); + const secondChildBuildNodes = document.querySelector('.js-pipeline-graph').querySelectorAll('.build:nth-child(2)'); for (const buildNodeIndex in secondChildBuildNodes) { const buildNode = secondChildBuildNodes[buildNodeIndex]; const firstChildBuildNode = buildNode.previousElementSibling; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index c7b5c1124b3..08d3443b3d0 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -24,7 +24,7 @@ in = time_interval_in_words pipeline.duration - .row-content-block.build-content.middle-block.hidden + .row-content-block.build-content.middle-block.js-pipeline-graph.hidden = render "projects/pipelines/graph", pipeline: pipeline - if pipeline.yaml_errors.present? -- cgit v1.2.1 From 52e0c4ba916d2cbc9bdb0fa0782c6b705c03c5a6 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 9 Dec 2016 16:16:14 +0000 Subject: Fix tests Fix broken tests --- app/assets/javascripts/pipelines.js.es6 | 5 ++++- app/views/projects/ci/builds/_build_pipeline.html.haml | 4 ++-- .../_generic_commit_status_pipeline.html.haml | 2 +- app/views/projects/pipelines/_with_tabs.html.haml | 2 +- spec/javascripts/fixtures/pipeline_graph.html.haml | 2 +- spec/views/projects/pipelines/show.html.haml_spec.rb | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index f09c6bb7def..fb95648e1c7 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -15,7 +15,9 @@ addMarginToBuildColumns() { this.pipelineGraph = document.querySelector('.js-pipeline-graph'); - const secondChildBuildNodes = document.querySelector('.js-pipeline-graph').querySelectorAll('.build:nth-child(2)'); + + const secondChildBuildNodes = this.pipelineGraph.querySelectorAll('.build:nth-child(2)'); + for (const buildNodeIndex in secondChildBuildNodes) { const buildNode = secondChildBuildNodes[buildNodeIndex]; const firstChildBuildNode = buildNode.previousElementSibling; @@ -28,6 +30,7 @@ const columnBuilds = previousColumn.querySelectorAll('.build'); if (columnBuilds.length === 1) previousColumn.classList.add('no-margin'); } + this.pipelineGraph.classList.remove('hidden'); } } diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 423a1282eb2..ad1a7360a8b 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,10 +1,10 @@ - is_playable = subject.playable? && can?(current_user, :update_build, @project) - if is_playable - = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.pipeline-graph', placement: 'bottom' } do + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.js-pipeline-graph', placement: 'bottom' } do = ci_icon_for_status('play') .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) - = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } do + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } do %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} = ci_icon_for_status(subject.status) .ci-status-text= subject.name diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 7b82d913d29..1bba0443154 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,4 +1,4 @@ -%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } } +%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } } - if subject.target_url = link_to subject.target_url do %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 739e5930822..88af41aa835 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -12,7 +12,7 @@ .tab-content #js-tab-pipeline.tab-pane - .build-content.middle-block + .build-content.middle-block.js-pipeline-graph = render "projects/pipelines/graph", pipeline: pipeline #js-tab-builds.tab-pane diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml index be5f105767c..deca50ceaa7 100644 --- a/spec/javascripts/fixtures/pipeline_graph.html.haml +++ b/spec/javascripts/fixtures/pipeline_graph.html.haml @@ -1,4 +1,4 @@ -%div.pipeline-visualization.pipeline-graph +%div.pipeline-visualization.js-pipeline-graph %ul.stage-column-list %li.stage-column .stage-name diff --git a/spec/views/projects/pipelines/show.html.haml_spec.rb b/spec/views/projects/pipelines/show.html.haml_spec.rb index bf027499c94..a066ea078e6 100644 --- a/spec/views/projects/pipelines/show.html.haml_spec.rb +++ b/spec/views/projects/pipelines/show.html.haml_spec.rb @@ -28,7 +28,7 @@ describe 'projects/pipelines/show' do it 'shows a graph with grouped stages' do render - expect(rendered).to have_css('.pipeline-graph') + expect(rendered).to have_css('.js-pipeline-graph') expect(rendered).to have_css('.grouped-pipeline-dropdown') # stages -- cgit v1.2.1 From 81a12c10fe7ba6f58ab4d1ae57861aa8899d545f Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 12 Dec 2016 09:55:29 +0100 Subject: API: Fix groups filter --- lib/api/groups.rb | 11 ++++++++++- spec/requests/api/groups_spec.rb | 11 +++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index fbf7513302b..105d3ee342e 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -1,7 +1,7 @@ module API class Groups < Grape::API include PaginationParams - + before { authenticate! } helpers do @@ -117,11 +117,20 @@ module API success Entities::Project end params do + optional :archived, type: Boolean, default: false, desc: 'Limit by archived status' + optional :visibility, type: String, values: %w[public internal private], + desc: 'Limit by visibility' + optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' + optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], + default: 'created_at', desc: 'Return projects ordered by field' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order' use :pagination end get ":id/projects" do group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) + projects = filter_projects(projects) present paginate(projects), with: Entities::Project, user: current_user end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 548ed8e1892..15647b262b6 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -245,6 +245,17 @@ describe API::Groups, api: true do expect(project_names).to match_array([project1.name, project3.name]) end + it 'filters the groups projects' do + public_projet = create(:project, :public, path: 'test1', group: group1) + + get api("/groups/#{group1.id}/projects", user1), visibility: 'public' + + expect(response).to have_http_status(200) + expect(json_response).to be_an(Array) + expect(json_response.length).to eq(1) + expect(json_response.first['name']).to eq(public_projet.name) + end + it "does not return a non existing group" do get api("/groups/1328/projects", user1) expect(response).to have_http_status(404) -- cgit v1.2.1 From 2f45d3bcf0f28d4cd4124b4c9722edc1d3085201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 9 Dec 2016 18:48:20 +0100 Subject: API: Memoize the current_user so that the sudo can work properly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The issue was arising when `#current_user` was called a second time after a user was impersonated: the `User#is_admin?` check would be performed on it and it would fail. Signed-off-by: Rémy Coutable --- changelogs/unreleased/25482-fix-api-sudo.yml | 4 + lib/api/helpers.rb | 131 +++++---- lib/api/users.rb | 2 +- spec/requests/api/api_helpers_spec.rb | 404 ------------------------- spec/requests/api/helpers_spec.rb | 425 +++++++++++++++++++++++++++ spec/requests/api/users_spec.rb | 27 +- 6 files changed, 523 insertions(+), 470 deletions(-) create mode 100644 changelogs/unreleased/25482-fix-api-sudo.yml delete mode 100644 spec/requests/api/api_helpers_spec.rb create mode 100644 spec/requests/api/helpers_spec.rb diff --git a/changelogs/unreleased/25482-fix-api-sudo.yml b/changelogs/unreleased/25482-fix-api-sudo.yml new file mode 100644 index 00000000000..3b23bfd3a21 --- /dev/null +++ b/changelogs/unreleased/25482-fix-api-sudo.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Memoize the current_user so that the sudo can work properly' +merge_request: 8017 +author: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 8b0f8deadfa..2041f0dac6b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -7,67 +7,23 @@ module API SUDO_HEADER = "HTTP_SUDO" SUDO_PARAM = :sudo - def private_token - params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER] - end - - def warden - env['warden'] - end - - # Check the Rails session for valid authentication details - # - # Until CSRF protection is added to the API, disallow this method for - # state-changing endpoints - def find_user_from_warden - warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) - end - def declared_params(options = {}) options = { include_parent_namespaces: false }.merge(options) declared(params, options).to_h.symbolize_keys end - def find_user_by_private_token - token = private_token - return nil unless token.present? - - User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) - end - def current_user - @current_user ||= find_user_by_private_token - @current_user ||= doorkeeper_guard - @current_user ||= find_user_from_warden - - unless @current_user && Gitlab::UserAccess.new(@current_user).allowed? - return nil - end - - identifier = sudo_identifier + return @current_user if defined?(@current_user) - if identifier - # We check for private_token because we cannot allow PAT to be used - forbidden!('Must be admin to use sudo') unless @current_user.is_admin? - forbidden!('Private token must be specified in order to use sudo') unless private_token_used? + @current_user = initial_current_user - @impersonator = @current_user - @current_user = User.by_username_or_id(identifier) - not_found!("No user id or username for: #{identifier}") if @current_user.nil? - end + sudo! @current_user end - def sudo_identifier - identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] - - # Regex for integers - if !!(identifier =~ /\A[0-9]+\z/) - identifier.to_i - else - identifier - end + def sudo? + initial_current_user != current_user end def user_project @@ -354,6 +310,79 @@ module API private + def private_token + params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER] + end + + def warden + env['warden'] + end + + # Check the Rails session for valid authentication details + # + # Until CSRF protection is added to the API, disallow this method for + # state-changing endpoints + def find_user_from_warden + warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) + end + + def find_user_by_private_token + token = private_token + return nil unless token.present? + + User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) + end + + def initial_current_user + return @initial_current_user if defined?(@initial_current_user) + + @initial_current_user ||= find_user_by_private_token + @initial_current_user ||= doorkeeper_guard + @initial_current_user ||= find_user_from_warden + + unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed? + @initial_current_user = nil + end + + @initial_current_user + end + + def sudo! + return unless sudo_identifier + return unless initial_current_user.is_a?(User) + + unless initial_current_user.is_admin? + forbidden!('Must be admin to use sudo') + end + + # Only private tokens should be used for the SUDO feature + unless private_token == initial_current_user.private_token + forbidden!('Private token must be specified in order to use sudo') + end + + sudoed_user = User.by_username_or_id(sudo_identifier) + + if sudoed_user + @current_user = sudoed_user + else + not_found!("No user id or username for: #{sudo_identifier}") + end + end + + def sudo_identifier + return @sudo_identifier if defined?(@sudo_identifier) + + identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] + + # Regex for integers + @sudo_identifier = + if !!(identifier =~ /\A[0-9]+\z/) + identifier.to_i + else + identifier + end + end + def add_pagination_headers(paginated_data) header 'X-Total', paginated_data.total_count.to_s header 'X-Total-Pages', paginated_data.total_pages.to_s @@ -386,10 +415,6 @@ module API links.join(', ') end - def private_token_used? - private_token == @current_user.private_token - end - def secret_token Gitlab::Shell.secret_token end diff --git a/lib/api/users.rb b/lib/api/users.rb index 1dab799dd61..c7db2d71017 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -353,7 +353,7 @@ module API success Entities::UserPublic end get do - present current_user, with: @impersonator ? Entities::UserWithPrivateToken : Entities::UserPublic + present current_user, with: sudo? ? Entities::UserWithPrivateToken : Entities::UserPublic end desc "Get the currently authenticated user's SSH keys" do diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb deleted file mode 100644 index 3f34309f419..00000000000 --- a/spec/requests/api/api_helpers_spec.rb +++ /dev/null @@ -1,404 +0,0 @@ -require 'spec_helper' - -describe API::Helpers, api: true do - include API::Helpers - include ApiHelpers - include SentryHelper - - let(:user) { create(:user) } - let(:admin) { create(:admin) } - let(:key) { create(:key, user: user) } - - let(:params) { {} } - let(:env) { { 'REQUEST_METHOD' => 'GET' } } - let(:request) { Rack::Request.new(env) } - - def set_env(token_usr, identifier) - clear_env - clear_param - env[API::Helpers::PRIVATE_TOKEN_HEADER] = token_usr.private_token - env[API::Helpers::SUDO_HEADER] = identifier - end - - def set_param(token_usr, identifier) - clear_env - clear_param - params[API::Helpers::PRIVATE_TOKEN_PARAM] = token_usr.private_token - params[API::Helpers::SUDO_PARAM] = identifier - end - - def clear_env - env.delete(API::Helpers::PRIVATE_TOKEN_HEADER) - env.delete(API::Helpers::SUDO_HEADER) - end - - def clear_param - params.delete(API::Helpers::PRIVATE_TOKEN_PARAM) - params.delete(API::Helpers::SUDO_PARAM) - end - - def warden_authenticate_returns(value) - warden = double("warden", authenticate: value) - env['warden'] = warden - end - - def doorkeeper_guard_returns(value) - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value } - end - - def error!(message, status) - raise Exception.new("#{status} - #{message}") - end - - describe ".current_user" do - subject { current_user } - - describe "Warden authentication" do - before { doorkeeper_guard_returns false } - - context "with invalid credentials" do - context "GET request" do - before { env['REQUEST_METHOD'] = 'GET' } - it { is_expected.to be_nil } - end - end - - context "with valid credentials" do - before { warden_authenticate_returns user } - - context "GET request" do - before { env['REQUEST_METHOD'] = 'GET' } - it { is_expected.to eq(user) } - end - - context "HEAD request" do - before { env['REQUEST_METHOD'] = 'HEAD' } - it { is_expected.to eq(user) } - end - - context "PUT request" do - before { env['REQUEST_METHOD'] = 'PUT' } - it { is_expected.to be_nil } - end - - context "POST request" do - before { env['REQUEST_METHOD'] = 'POST' } - it { is_expected.to be_nil } - end - - context "DELETE request" do - before { env['REQUEST_METHOD'] = 'DELETE' } - it { is_expected.to be_nil } - end - end - end - - describe "when authenticating using a user's private token" do - it "returns nil for an invalid token" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token' - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - - it "returns nil for a user without access" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token - allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) - expect(current_user).to be_nil - end - - it "leaves user as is when sudo not specified" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token - expect(current_user).to eq(user) - clear_env - params[API::Helpers::PRIVATE_TOKEN_PARAM] = user.private_token - expect(current_user).to eq(user) - end - end - - describe "when authenticating using a user's personal access tokens" do - let(:personal_access_token) { create(:personal_access_token, user: user) } - - it "returns nil for an invalid token" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token' - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - - it "returns nil for a user without access" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) - expect(current_user).to be_nil - end - - it "leaves user as is when sudo not specified" do - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - expect(current_user).to eq(user) - clear_env - params[API::Helpers::PRIVATE_TOKEN_PARAM] = personal_access_token.token - expect(current_user).to eq(user) - end - - it 'does not allow revoked tokens' do - personal_access_token.revoke! - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - - it 'does not allow expired tokens' do - personal_access_token.update_attributes!(expires_at: 1.day.ago) - env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token - allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } - expect(current_user).to be_nil - end - end - - context 'sudo usage' do - context 'with admin' do - context 'with header' do - context 'with id' do - it 'changes current_user to sudo' do - set_env(admin, user.id) - - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_env(admin, admin.id) - - expect(current_user).to eq(admin) - end - - it 'throws an error when user cannot be found' do - id = user.id + admin.id - expect(user.id).not_to eq(id) - expect(admin.id).not_to eq(id) - - set_env(admin, id) - - expect { current_user }.to raise_error(Exception) - end - end - - context 'with username' do - it 'changes current_user to sudo' do - set_env(admin, user.username) - - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_env(admin, admin.username) - - expect(current_user).to eq(admin) - end - - it "throws an error when the user cannot be found for a given username" do - username = "#{user.username}#{admin.username}" - expect(user.username).not_to eq(username) - expect(admin.username).not_to eq(username) - - set_env(admin, username) - - expect { current_user }.to raise_error(Exception) - end - end - end - - context 'with param' do - context 'with id' do - it 'changes current_user to sudo' do - set_param(admin, user.id) - - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_param(admin, admin.id) - - expect(current_user).to eq(admin) - end - - it 'handles sudo to oneself using string' do - set_env(admin, user.id.to_s) - - expect(current_user).to eq(user) - end - - it 'throws an error when user cannot be found' do - id = user.id + admin.id - expect(user.id).not_to eq(id) - expect(admin.id).not_to eq(id) - - set_param(admin, id) - - expect { current_user }.to raise_error(Exception) - end - end - - context 'with username' do - it 'changes current_user to sudo' do - set_param(admin, user.username) - - expect(current_user).to eq(user) - end - - it 'handles sudo to oneself' do - set_param(admin, admin.username) - - expect(current_user).to eq(admin) - end - - it "throws an error when the user cannot be found for a given username" do - username = "#{user.username}#{admin.username}" - expect(user.username).not_to eq(username) - expect(admin.username).not_to eq(username) - - set_param(admin, username) - - expect { current_user }.to raise_error(Exception) - end - end - end - end - - context 'with regular user' do - context 'with env' do - it 'changes current_user to sudo when admin and user id' do - set_env(user, admin.id) - - expect { current_user }.to raise_error(Exception) - end - - it 'changes current_user to sudo when admin and user username' do - set_env(user, admin.username) - - expect { current_user }.to raise_error(Exception) - end - end - - context 'with params' do - it 'changes current_user to sudo when admin and user id' do - set_param(user, admin.id) - - expect { current_user }.to raise_error(Exception) - end - - it 'changes current_user to sudo when admin and user username' do - set_param(user, admin.username) - - expect { current_user }.to raise_error(Exception) - end - end - end - end - end - - describe '.sudo_identifier' do - it "returns integers when input is an int" do - set_env(admin, '123') - expect(sudo_identifier).to eq(123) - set_env(admin, '0001234567890') - expect(sudo_identifier).to eq(1234567890) - - set_param(admin, '123') - expect(sudo_identifier).to eq(123) - set_param(admin, '0001234567890') - expect(sudo_identifier).to eq(1234567890) - end - - it "returns string when input is an is not an int" do - set_env(admin, '12.30') - expect(sudo_identifier).to eq("12.30") - set_env(admin, 'hello') - expect(sudo_identifier).to eq('hello') - set_env(admin, ' 123') - expect(sudo_identifier).to eq(' 123') - - set_param(admin, '12.30') - expect(sudo_identifier).to eq("12.30") - set_param(admin, 'hello') - expect(sudo_identifier).to eq('hello') - set_param(admin, ' 123') - expect(sudo_identifier).to eq(' 123') - end - end - - describe '.handle_api_exception' do - before do - allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true) - allow_any_instance_of(self.class).to receive(:rack_response) - end - - it 'does not report a MethodNotAllowed exception to Sentry' do - exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' }) - allow(exception).to receive(:backtrace).and_return(caller) - - expect(Raven).not_to receive(:capture_exception).with(exception) - - handle_api_exception(exception) - end - - it 'does report RuntimeError to Sentry' do - exception = RuntimeError.new('test error') - allow(exception).to receive(:backtrace).and_return(caller) - - expect_any_instance_of(self.class).to receive(:sentry_context) - expect(Raven).to receive(:capture_exception).with(exception) - - handle_api_exception(exception) - end - end - - describe '.authenticate_non_get!' do - %w[HEAD GET].each do |method_name| - context "method is #{method_name}" do - before do - expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) - end - - it 'does not raise an error' do - expect_any_instance_of(self.class).not_to receive(:authenticate!) - - expect { authenticate_non_get! }.not_to raise_error - end - end - end - - %w[POST PUT PATCH DELETE].each do |method_name| - context "method is #{method_name}" do - before do - expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) - end - - it 'calls authenticate!' do - expect_any_instance_of(self.class).to receive(:authenticate!) - - authenticate_non_get! - end - end - end - end - - describe '.authenticate!' do - context 'current_user is nil' do - before do - expect_any_instance_of(self.class).to receive(:current_user).and_return(nil) - end - - it 'returns a 401 response' do - expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}' - end - end - - context 'current_user is present' do - before do - expect_any_instance_of(self.class).to receive(:current_user).and_return(true) - end - - it 'does not raise an error' do - expect { authenticate! }.not_to raise_error - end - end - end -end diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb new file mode 100644 index 00000000000..573154f69b4 --- /dev/null +++ b/spec/requests/api/helpers_spec.rb @@ -0,0 +1,425 @@ +require 'spec_helper' + +describe API::Helpers, api: true do + include API::Helpers + include SentryHelper + + let(:user) { create(:user) } + let(:admin) { create(:admin) } + let(:key) { create(:key, user: user) } + + let(:params) { {} } + let(:env) { { 'REQUEST_METHOD' => 'GET' } } + let(:request) { Rack::Request.new(env) } + + def set_env(user_or_token, identifier) + clear_env + clear_param + env[API::Helpers::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token + env[API::Helpers::SUDO_HEADER] = identifier + end + + def set_param(user_or_token, identifier) + clear_env + clear_param + params[API::Helpers::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token + params[API::Helpers::SUDO_PARAM] = identifier + end + + def clear_env + env.delete(API::Helpers::PRIVATE_TOKEN_HEADER) + env.delete(API::Helpers::SUDO_HEADER) + end + + def clear_param + params.delete(API::Helpers::PRIVATE_TOKEN_PARAM) + params.delete(API::Helpers::SUDO_PARAM) + end + + def warden_authenticate_returns(value) + warden = double("warden", authenticate: value) + env['warden'] = warden + end + + def doorkeeper_guard_returns(value) + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value } + end + + def error!(message, status) + raise Exception.new("#{status} - #{message}") + end + + describe ".current_user" do + subject { current_user } + + describe "Warden authentication" do + before { doorkeeper_guard_returns false } + + context "with invalid credentials" do + context "GET request" do + before { env['REQUEST_METHOD'] = 'GET' } + it { is_expected.to be_nil } + end + end + + context "with valid credentials" do + before { warden_authenticate_returns user } + + context "GET request" do + before { env['REQUEST_METHOD'] = 'GET' } + it { is_expected.to eq(user) } + end + + context "HEAD request" do + before { env['REQUEST_METHOD'] = 'HEAD' } + it { is_expected.to eq(user) } + end + + context "PUT request" do + before { env['REQUEST_METHOD'] = 'PUT' } + it { is_expected.to be_nil } + end + + context "POST request" do + before { env['REQUEST_METHOD'] = 'POST' } + it { is_expected.to be_nil } + end + + context "DELETE request" do + before { env['REQUEST_METHOD'] = 'DELETE' } + it { is_expected.to be_nil } + end + end + end + + describe "when authenticating using a user's private token" do + it "returns nil for an invalid token" do + env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token' + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } + expect(current_user).to be_nil + end + + it "returns nil for a user without access" do + env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token + allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) + expect(current_user).to be_nil + end + + it "leaves user as is when sudo not specified" do + env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token + expect(current_user).to eq(user) + clear_env + params[API::Helpers::PRIVATE_TOKEN_PARAM] = user.private_token + expect(current_user).to eq(user) + end + end + + describe "when authenticating using a user's personal access tokens" do + let(:personal_access_token) { create(:personal_access_token, user: user) } + + it "returns nil for an invalid token" do + env[API::Helpers::PRIVATE_TOKEN_HEADER] = 'invalid token' + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } + expect(current_user).to be_nil + end + + it "returns nil for a user without access" do + env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token + allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false) + expect(current_user).to be_nil + end + + it "leaves user as is when sudo not specified" do + env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token + expect(current_user).to eq(user) + clear_env + params[API::Helpers::PRIVATE_TOKEN_PARAM] = personal_access_token.token + expect(current_user).to eq(user) + end + + it 'does not allow revoked tokens' do + personal_access_token.revoke! + env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } + expect(current_user).to be_nil + end + + it 'does not allow expired tokens' do + personal_access_token.update_attributes!(expires_at: 1.day.ago) + env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } + expect(current_user).to be_nil + end + end + + context 'sudo usage' do + context 'with admin' do + context 'with header' do + context 'with id' do + it 'changes current_user to sudo' do + set_env(admin, user.id) + + expect(current_user).to eq(user) + end + + it 'memoize the current_user: sudo permissions are not run against the sudoed user' do + set_env(admin, user.id) + + expect(current_user).to eq(user) + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_env(admin, admin.id) + + expect(current_user).to eq(admin) + end + + it 'throws an error when user cannot be found' do + id = user.id + admin.id + expect(user.id).not_to eq(id) + expect(admin.id).not_to eq(id) + + set_env(admin, id) + + expect { current_user }.to raise_error(Exception) + end + end + + context 'with username' do + it 'changes current_user to sudo' do + set_env(admin, user.username) + + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_env(admin, admin.username) + + expect(current_user).to eq(admin) + end + + it "throws an error when the user cannot be found for a given username" do + username = "#{user.username}#{admin.username}" + expect(user.username).not_to eq(username) + expect(admin.username).not_to eq(username) + + set_env(admin, username) + + expect { current_user }.to raise_error(Exception) + end + end + end + + context 'with param' do + context 'with id' do + it 'changes current_user to sudo' do + set_param(admin, user.id) + + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_param(admin, admin.id) + + expect(current_user).to eq(admin) + end + + it 'handles sudo to oneself using string' do + set_env(admin, user.id.to_s) + + expect(current_user).to eq(user) + end + + it 'throws an error when user cannot be found' do + id = user.id + admin.id + expect(user.id).not_to eq(id) + expect(admin.id).not_to eq(id) + + set_param(admin, id) + + expect { current_user }.to raise_error(Exception) + end + end + + context 'with username' do + it 'changes current_user to sudo' do + set_param(admin, user.username) + + expect(current_user).to eq(user) + end + + it 'handles sudo to oneself' do + set_param(admin, admin.username) + + expect(current_user).to eq(admin) + end + + it "throws an error when the user cannot be found for a given username" do + username = "#{user.username}#{admin.username}" + expect(user.username).not_to eq(username) + expect(admin.username).not_to eq(username) + + set_param(admin, username) + + expect { current_user }.to raise_error(Exception) + end + end + end + end + + context 'with regular user' do + context 'with env' do + it 'changes current_user to sudo when admin and user id' do + set_env(user, admin.id) + + expect { current_user }.to raise_error(Exception) + end + + it 'changes current_user to sudo when admin and user username' do + set_env(user, admin.username) + + expect { current_user }.to raise_error(Exception) + end + end + + context 'with params' do + it 'changes current_user to sudo when admin and user id' do + set_param(user, admin.id) + + expect { current_user }.to raise_error(Exception) + end + + it 'changes current_user to sudo when admin and user username' do + set_param(user, admin.username) + + expect { current_user }.to raise_error(Exception) + end + end + end + end + end + + describe '.sudo?' do + context 'when no sudo env or param is passed' do + before do + doorkeeper_guard_returns(nil) + end + + it 'returns false' do + expect(sudo?).to be_falsy + end + end + + context 'when sudo env or param is passed', 'user is not an admin' do + before do + set_env(user, '123') + end + + it 'returns an 403 Forbidden' do + expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Must be admin to use sudo"}' + end + end + + context 'when sudo env or param is passed', 'user is admin' do + context 'personal access token is used' do + before do + personal_access_token = create(:personal_access_token, user: admin) + set_env(personal_access_token.token, user.id) + end + + it 'returns an 403 Forbidden' do + expect { sudo? }.to raise_error '403 - {"message"=>"403 Forbidden - Private token must be specified in order to use sudo"}' + end + end + + context 'private access token is used' do + before do + set_env(admin.private_token, user.id) + end + + it 'returns true' do + expect(sudo?).to be_truthy + end + end + end + end + + describe '.handle_api_exception' do + before do + allow_any_instance_of(self.class).to receive(:sentry_enabled?).and_return(true) + allow_any_instance_of(self.class).to receive(:rack_response) + end + + it 'does not report a MethodNotAllowed exception to Sentry' do + exception = Grape::Exceptions::MethodNotAllowed.new({ 'X-GitLab-Test' => '1' }) + allow(exception).to receive(:backtrace).and_return(caller) + + expect(Raven).not_to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + + it 'does report RuntimeError to Sentry' do + exception = RuntimeError.new('test error') + allow(exception).to receive(:backtrace).and_return(caller) + + expect_any_instance_of(self.class).to receive(:sentry_context) + expect(Raven).to receive(:capture_exception).with(exception) + + handle_api_exception(exception) + end + end + + describe '.authenticate_non_get!' do + %w[HEAD GET].each do |method_name| + context "method is #{method_name}" do + before do + expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + end + + it 'does not raise an error' do + expect_any_instance_of(self.class).not_to receive(:authenticate!) + + expect { authenticate_non_get! }.not_to raise_error + end + end + end + + %w[POST PUT PATCH DELETE].each do |method_name| + context "method is #{method_name}" do + before do + expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + end + + it 'calls authenticate!' do + expect_any_instance_of(self.class).to receive(:authenticate!) + + authenticate_non_get! + end + end + end + end + + describe '.authenticate!' do + context 'current_user is nil' do + before do + expect_any_instance_of(self.class).to receive(:current_user).and_return(nil) + end + + it 'returns a 401 response' do + expect { authenticate! }.to raise_error '401 - {"message"=>"401 Unauthorized"}' + end + end + + context 'current_user is present' do + before do + expect_any_instance_of(self.class).to receive(:current_user).and_return(true) + end + + it 'does not raise an error' do + expect { authenticate! }.not_to raise_error + end + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c37dbfa0a33..9e317f3a7e9 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -651,13 +651,12 @@ describe API::Users, api: true do end describe "GET /user" do - let(:personal_access_token) { create(:personal_access_token, user: user) } - let(:private_token) { user.private_token } + let(:personal_access_token) { create(:personal_access_token, user: user).token } context 'with regular user' do context 'with personal access token' do it 'returns 403 without private token when sudo is defined' do - get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}") + get api("/user?private_token=#{personal_access_token}&sudo=123") expect(response).to have_http_status(403) end @@ -665,7 +664,7 @@ describe API::Users, api: true do context 'with private token' do it 'returns 403 without private token when sudo defined' do - get api("/user?private_token=#{private_token}&sudo=#{user.id}") + get api("/user?private_token=#{user.private_token}&sudo=123") expect(response).to have_http_status(403) end @@ -676,40 +675,44 @@ describe API::Users, api: true do expect(response).to have_http_status(200) expect(response).to match_response_schema('user/public') + expect(json_response['id']).to eq(user.id) end end context 'with admin' do - let(:user) { create(:admin) } + let(:admin_personal_access_token) { create(:personal_access_token, user: admin).token } context 'with personal access token' do it 'returns 403 without private token when sudo defined' do - get api("/user?private_token=#{personal_access_token.token}&sudo=#{user.id}") + get api("/user?private_token=#{admin_personal_access_token}&sudo=#{user.id}") expect(response).to have_http_status(403) end - it 'returns current user without private token when sudo not defined' do - get api("/user?private_token=#{personal_access_token.token}") + it 'returns initial current user without private token when sudo not defined' do + get api("/user?private_token=#{admin_personal_access_token}") expect(response).to have_http_status(200) expect(response).to match_response_schema('user/public') + expect(json_response['id']).to eq(admin.id) end end context 'with private token' do - it 'returns current user with private token when sudo defined' do - get api("/user?private_token=#{private_token}&sudo=#{user.id}") + it 'returns sudoed user with private token when sudo defined' do + get api("/user?private_token=#{admin.private_token}&sudo=#{user.id}") expect(response).to have_http_status(200) expect(response).to match_response_schema('user/login') + expect(json_response['id']).to eq(user.id) end - it 'returns current user without private token when sudo not defined' do - get api("/user?private_token=#{private_token}") + it 'returns initial current user without private token when sudo not defined' do + get api("/user?private_token=#{admin.private_token}") expect(response).to have_http_status(200) expect(response).to match_response_schema('user/public') + expect(json_response['id']).to eq(admin.id) end end end -- cgit v1.2.1 From 62f8717c035f8d287324d27563b3a42fd27839d6 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Fri, 25 Nov 2016 18:42:25 +0000 Subject: Added hiddenInterval and immediateExecution settings, fixed visibilitychange listening, implemented with mr widget Updated tests Added tests Review changes --- app/assets/javascripts/merge_request_widget.js.es6 | 74 ++++++++-------------- app/assets/javascripts/smart_interval.js.es6 | 69 ++++++++++++++------ .../unreleased/24807-stop-ddosing-ourselves.yml | 4 ++ spec/javascripts/smart_interval_spec.js.es6 | 35 ++++++++-- 4 files changed, 105 insertions(+), 77 deletions(-) create mode 100644 changelogs/unreleased/24807-stop-ddosing-ourselves.yml diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index d9495e50388..7022aa1263b 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -40,19 +40,26 @@ $('#modal_merge_info').modal({ show: false }); - this.firstCICheck = true; - this.readyForCICheck = false; - this.readyForCIEnvironmentCheck = false; - this.cancel = false; - clearInterval(this.fetchBuildStatusInterval); - clearInterval(this.fetchBuildEnvironmentStatusInterval); this.clearEventListeners(); this.addEventListeners(); this.getCIStatus(false); - this.getCIEnvironmentsStatus(); this.retrieveSuccessIcon(); - this.pollCIStatus(); - this.pollCIEnvironmentsStatus(); + + this.ciStatusInterval = new global.SmartInterval({ + callback: this.getCIStatus.bind(this, true), + startingInterval: 10000, + maxInterval: 30000, + hiddenInterval: 120000, + incrementByFactorOf: 5000, + }); + this.ciEnvironmentStatusInterval = new global.SmartInterval({ + callback: this.getCIEnvironmentsStatus.bind(this), + startingInterval: 30000, + maxInterval: 120000, + hiddenInterval: 240000, + incrementByFactorOf: 15000, + immediateExecution: true, + }); notifyPermissions(); } @@ -60,10 +67,6 @@ return $(document).off('page:change.merge_request'); }; - MergeRequestWidget.prototype.cancelPolling = function() { - return this.cancel = true; - }; - MergeRequestWidget.prototype.addEventListeners = function() { var allowedPages; allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; @@ -72,9 +75,6 @@ var page; page = $('body').data('page').split(':').last(); if (allowedPages.indexOf(page) < 0) { - clearInterval(_this.fetchBuildStatusInterval); - clearInterval(_this.fetchBuildEnvironmentStatusInterval); - _this.cancelPolling(); return _this.clearEventListeners(); } }; @@ -114,6 +114,11 @@ }); }; + MergeRequestWidget.prototype.cancelPolling = function () { + this.ciStatusInterval.cancel(); + this.ciEnvironmentStatusInterval.cancel(); + }; + MergeRequestWidget.prototype.getMergeStatus = function() { return $.get(this.opts.merge_check_url, function(data) { return $('.mr-state-widget').replaceWith(data); @@ -131,18 +136,6 @@ } }; - MergeRequestWidget.prototype.pollCIStatus = function() { - return this.fetchBuildStatusInterval = setInterval(((function(_this) { - return function() { - if (!_this.readyForCICheck) { - return; - } - _this.getCIStatus(true); - return _this.readyForCICheck = false; - }; - })(this)), 10000); - }; - MergeRequestWidget.prototype.getCIStatus = function(showNotification) { var _this; _this = this; @@ -150,23 +143,17 @@ return $.getJSON(this.opts.ci_status_url, (function(_this) { return function(data) { var message, status, title; - if (_this.cancel) { - return; - } - _this.readyForCICheck = true; if (data.status === '') { return; } if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); - if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { + if (data.status !== _this.opts.ci_status && (data.status != null)) { _this.opts.ci_status = data.status; _this.showCIStatus(data.status); if (data.coverage) { _this.showCICoverage(data.coverage); } - // The first check should only update the UI, a notification - // should only be displayed on status changes - if (showNotification && !_this.firstCICheck) { + if (showNotification) { status = _this.ciLabelForStatus(data.status); if (status === "preparing") { title = _this.opts.ci_title.preparing; @@ -184,24 +171,13 @@ return Turbolinks.visit(_this.opts.builds_path); }); } - return _this.firstCICheck = false; } }; })(this)); }; - MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() { - this.fetchBuildEnvironmentStatusInterval = setInterval(() => { - if (!this.readyForCIEnvironmentCheck) return; - this.getCIEnvironmentsStatus(); - this.readyForCIEnvironmentCheck = false; - }, 300000); - }; - MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() { $.getJSON(this.opts.ci_environments_status_url, (environments) => { - if (this.cancel) return; - this.readyForCIEnvironmentCheck = true; if (environments && environments.length) this.renderEnvironments(environments); }); }; @@ -212,11 +188,11 @@ if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); - + if (!environment.stop_url) { $('.js-stop-env-link', $template).remove(); } - + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = gl.utils.getTimeago().format(environment.deployed_at, 'gl_en') + '.'; } else { diff --git a/app/assets/javascripts/smart_interval.js.es6 b/app/assets/javascripts/smart_interval.js.es6 index 5eb15dba79b..40f67637c7c 100644 --- a/app/assets/javascripts/smart_interval.js.es6 +++ b/app/assets/javascripts/smart_interval.js.es6 @@ -7,24 +7,31 @@ (() => { class SmartInterval { /** - * @param { function } callback Function to be called on each iteration (required) - * @param { milliseconds } startingInterval `currentInterval` is set to this initially - * @param { milliseconds } maxInterval `currentInterval` will be incremented to this - * @param { integer } incrementByFactorOf `currentInterval` is incremented by this factor - * @param { boolean } lazyStart Configure if timer is initialized on instantiation or lazily + * @param { function } opts.callback Function to be called on each iteration (required) + * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially + * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this + * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this + * when the page is hidden + * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor + * @param { boolean } opts.lazyStart Configure if timer is initialized on + * instantiation or lazily + * @param { boolean } opts.immediateExecution Configure if callback should + * be executed before the first interval. */ - constructor({ callback, startingInterval, maxInterval, incrementByFactorOf, lazyStart }) { + constructor(opts = {}) { this.cfg = { - callback, - startingInterval, - maxInterval, - incrementByFactorOf, - lazyStart, + callback: opts.callback, + startingInterval: opts.startingInterval, + maxInterval: opts.maxInterval, + hiddenInterval: opts.hiddenInterval, + incrementByFactorOf: opts.incrementByFactorOf, + lazyStart: opts.lazyStart, + immediateExecution: opts.immediateExecution, }; this.state = { intervalId: null, - currentInterval: startingInterval, + currentInterval: this.cfg.startingInterval, pageVisibility: 'visible', }; @@ -36,6 +43,11 @@ const cfg = this.cfg; const state = this.state; + if (cfg.immediateExecution) { + cfg.immediateExecution = false; + cfg.callback(); + } + state.intervalId = window.setInterval(() => { cfg.callback(); @@ -54,14 +66,29 @@ this.stopTimer(); } + onVisibilityHidden() { + if (this.cfg.hiddenInterval) { + this.setCurrentInterval(this.cfg.hiddenInterval); + this.resume(); + } else { + this.cancel(); + } + } + // start a timer, using the existing interval resume() { this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped this.start(); } + onVisibilityVisible() { + this.cancel(); + this.start(); + } + destroy() { this.cancel(); + document.removeEventListener('visibilitychange', this.handleVisibilityChange); $(document).off('visibilitychange').off('page:before-unload'); } @@ -80,11 +107,7 @@ initVisibilityChangeHandling() { // cancel interval when tab no longer shown (prevents cached pages from polling) - $(document) - .off('visibilitychange').on('visibilitychange', (e) => { - this.state.pageVisibility = e.target.visibilityState; - this.handleVisibilityChange(); - }); + document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); } initPageUnloadHandling() { @@ -92,10 +115,11 @@ $(document).on('page:before-unload', () => this.cancel()); } - handleVisibilityChange() { - const state = this.state; - - const intervalAction = state.pageVisibility === 'hidden' ? this.cancel : this.resume; + handleVisibilityChange(e) { + this.state.pageVisibility = e.target.visibilityState; + const intervalAction = this.isPageVisible() ? + this.onVisibilityVisible : + this.onVisibilityHidden; intervalAction.apply(this); } @@ -111,6 +135,7 @@ incrementInterval() { const cfg = this.cfg; const currentInterval = this.getCurrentInterval(); + if (cfg.hiddenInterval && !this.isPageVisible()) return; let nextInterval = currentInterval * cfg.incrementByFactorOf; if (nextInterval > cfg.maxInterval) { @@ -120,6 +145,8 @@ this.setCurrentInterval(nextInterval); } + isPageVisible() { return this.state.pageVisibility === 'visible'; } + stopTimer() { const state = this.state; diff --git a/changelogs/unreleased/24807-stop-ddosing-ourselves.yml b/changelogs/unreleased/24807-stop-ddosing-ourselves.yml new file mode 100644 index 00000000000..49e6c5e56e5 --- /dev/null +++ b/changelogs/unreleased/24807-stop-ddosing-ourselves.yml @@ -0,0 +1,4 @@ +--- +title: Use SmartInterval for MR widget and improve visibilitychange functionality +merge_request: 7762 +author: diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js.es6 index ed6166a25a8..1b7ca97cde4 100644 --- a/spec/javascripts/smart_interval_spec.js.es6 +++ b/spec/javascripts/smart_interval_spec.js.es6 @@ -14,8 +14,9 @@ startingInterval: DEFAULT_STARTING_INTERVAL, maxInterval: DEFAULT_MAX_INTERVAL, incrementByFactorOf: DEFAULT_INCREMENT_FACTOR, - delayStartBy: 0, lazyStart: false, + immediateExecution: false, + hiddenInterval: null, }; if (config) { @@ -114,14 +115,31 @@ expect(interval.state.intervalId).toBeTruthy(); // simulates triggering of visibilitychange event - interval.state.pageVisibility = 'hidden'; - interval.handleVisibilityChange(); + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); expect(interval.state.intervalId).toBeUndefined(); done(); }, DEFAULT_SHORT_TIMEOUT); }); + it('should change to the hidden interval when page is not visible', function (done) { + const HIDDEN_INTERVAL = 1500; + const interval = createDefaultSmartInterval({ hiddenInterval: HIDDEN_INTERVAL }); + + setTimeout(() => { + expect(interval.state.intervalId).toBeTruthy(); + expect(interval.getCurrentInterval() >= DEFAULT_STARTING_INTERVAL && + interval.getCurrentInterval() <= DEFAULT_MAX_INTERVAL).toBeTruthy(); + + // simulates triggering of visibilitychange event + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); + + expect(interval.state.intervalId).toBeTruthy(); + expect(interval.getCurrentInterval()).toBe(HIDDEN_INTERVAL); + done(); + }, DEFAULT_SHORT_TIMEOUT); + }); + it('should resume when page is becomes visible at the previous interval', function (done) { const interval = this.smartInterval; @@ -129,14 +147,12 @@ expect(interval.state.intervalId).toBeTruthy(); // simulates triggering of visibilitychange event - interval.state.pageVisibility = 'hidden'; - interval.handleVisibilityChange(); + interval.handleVisibilityChange({ target: { visibilityState: 'hidden' } }); expect(interval.state.intervalId).toBeUndefined(); // simulates triggering of visibilitychange event - interval.state.pageVisibility = 'visible'; - interval.handleVisibilityChange(); + interval.handleVisibilityChange({ target: { visibilityState: 'visible' } }); expect(interval.state.intervalId).toBeTruthy(); @@ -154,6 +170,11 @@ done(); }, DEFAULT_SHORT_TIMEOUT); }); + + it('should execute callback before first interval', function () { + const interval = createDefaultSmartInterval({ immediateExecution: true }); + expect(interval.cfg.immediateExecution).toBeFalsy(); + }); }); }); })(window.gl || (window.gl = {})); -- cgit v1.2.1 From ffafd0973100a53efc46011ca4415e8385e0cc07 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 14:14:35 +0100 Subject: Fix build stop extended status CSS class --- lib/gitlab/ci/status/build/stop.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index 487fd033960..76c66fab323 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -17,10 +17,6 @@ module Gitlab 'icon_status_skipped' end - def to_s - 'stop' - end - def has_action? can?(user, :update_build, subject) end -- cgit v1.2.1 From 8d0645c2ce3769268020ad6f8e51db07fb1e4bc6 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 12 Dec 2016 14:27:52 +0100 Subject: Grapify the service API --- doc/api/services.md | 22 ++++++++++++++++------ .../project_services/hipchat_service_spec.rb | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/doc/api/services.md b/doc/api/services.md index acb54448664..3dad953cd1e 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -153,9 +153,12 @@ PUT /projects/:id/services/builds-email Parameters: -- `recipients` (**required**) - Comma-separated list of recipient email addresses -- `add_pusher` (optional) - Add pusher to recipients list -- `notify_only_broken_builds` (optional) -Notify only broken builds +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recipients` | string | yes | Comma-separated list of recipient email addresses | +| `add_pusher` | boolean | no | Add pusher to recipients list | +| `notify_only_broken_builds` | boolean | no | Notify only broken builds | + ### Delete Build-Emails service @@ -538,7 +541,10 @@ PUT /projects/:id/services/mattermost-slash-commands Parameters: -- `token` (**required**) - The Mattermost token +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `token` | string | yes | The Mattermost token | + ### Delete Mattermost Slash Command service @@ -570,8 +576,12 @@ PUT /projects/:id/services/pipelines-email Parameters: -- `recipients` (**required**) - Comma-separated list of recipient email addresses -- `notify_only_broken_builds` (optional) -Notify only broken pipelines +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `recipients` | string | yes | Comma-separated list of recipient email addresses | +| `add_pusher` | boolean | no | Add pusher to recipients list | +| `notify_only_broken_builds` | boolean | no | Notify only broken pipelines | + ### Delete Pipeline-Emails service diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 564e49d5459..2da3a9cb09f 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -358,7 +358,7 @@ describe HipchatService, models: true do context 'with a failed build' do it 'uses the red color' do build_data = { object_kind: 'build', commit: { status: 'failed' } } - + expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' }) end end -- cgit v1.2.1 From 17e3d3fde8c9a83f58d797c5f62f36b59eedd870 Mon Sep 17 00:00:00 2001 From: winniehell Date: Tue, 6 Dec 2016 01:29:43 +0100 Subject: Avoid escaping relative links in Markdown twice (!7940) --- changelogs/unreleased/unescape-relative-path.yml | 4 ++++ lib/banzai/filter/relative_link_filter.rb | 14 ++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/unescape-relative-path.yml diff --git a/changelogs/unreleased/unescape-relative-path.yml b/changelogs/unreleased/unescape-relative-path.yml new file mode 100644 index 00000000000..755b0379a16 --- /dev/null +++ b/changelogs/unreleased/unescape-relative-path.yml @@ -0,0 +1,4 @@ +--- +title: Avoid escaping relative links in Markdown twice +merge_request: 7940 +author: winniehell diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index f09d78be0ce..9e23c8f8c55 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -46,7 +46,7 @@ module Banzai end def rebuild_relative_uri(uri) - file_path = relative_file_path(uri.path) + file_path = relative_file_path(uri) uri.path = [ relative_url_root, @@ -59,8 +59,10 @@ module Banzai uri end - def relative_file_path(path) - nested_path = build_relative_path(path, context[:requested_path]) + def relative_file_path(uri) + path = Addressable::URI.unescape(uri.path) + request_path = Addressable::URI.unescape(context[:requested_path]) + nested_path = build_relative_path(path, request_path) file_exists?(nested_path) ? nested_path : path end @@ -108,11 +110,7 @@ module Banzai end def uri_type(path) - @uri_types[path] ||= begin - unescaped_path = Addressable::URI.unescape(path) - - current_commit.uri_type(unescaped_path) - end + @uri_types[path] ||= current_commit.uri_type(path) end def current_commit -- cgit v1.2.1 From f91e8269c18ac80d6f43fdd7b87362e5c9ccbc94 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 14:53:05 +0100 Subject: Add tests for build play extended detailed status --- spec/factories/ci/builds.rb | 13 ++++-- spec/lib/gitlab/ci/status/build/play_spec.rb | 59 ++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 spec/lib/gitlab/ci/status/build/play_spec.rb diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index c443af09075..62466c06194 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -12,12 +12,14 @@ FactoryGirl.define do started_at 'Di 29. Okt 09:51:28 CET 2013' finished_at 'Di 29. Okt 09:53:28 CET 2013' commands 'ls -a' + options do { image: "ruby:2.1", services: ["postgres"] } end + yaml_variables do [ { key: :DB_NAME, value: 'postgres', public: true } @@ -60,15 +62,20 @@ FactoryGirl.define do end trait :teardown_environment do - options do - { environment: { action: 'stop' } } - end + environment 'staging' + options environment: { name: 'staging', + action: 'stop' } end trait :allowed_to_fail do allow_failure true end + trait :playable do + skipped + manual + end + after(:build) do |build, evaluator| build.project = build.pipeline.project end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb new file mode 100644 index 00000000000..ae103d8993d --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Play do + let(:core_status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(core_status) + end + + describe '#text' do + it { expect(subject.text).to eq 'play' } + end + + describe '#label' do + it { expect(subject.label).to eq 'play' } + end + + describe '#icon' do + it 'does not override core status icon' do + expect(core_status).to receive(:icon) + + subject.icon + end + end + + describe '.matches?' do + context 'build is playable' do + context 'when build stops an environment' do + let(:build) do + create(:ci_build, :playable, :teardown_environment) + end + + it 'does not match' do + expect(described_class.matches?(build, user)) + .to be false + end + end + + context 'when build does not stop an environment' do + let(:build) { create(:ci_build, :playable) } + + it 'is a correct match' do + expect(described_class.matches?(build, user)) + .to be true + end + end + end + + context 'when build is not playable' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(described_class.matches?(build, user)) + .to be false + end + end + end +end -- cgit v1.2.1 From ab37be2dda8188c56d6e76a23b46ccc88c42baa8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 14:59:46 +0100 Subject: Add specs for build stop extended detailed status --- lib/gitlab/ci/status/build/stop.rb | 4 -- spec/lib/gitlab/ci/status/build/stop_spec.rb | 59 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 spec/lib/gitlab/ci/status/build/stop_spec.rb diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index 76c66fab323..a4966a55892 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -13,10 +13,6 @@ module Gitlab 'stop' end - def icon - 'icon_status_skipped' - end - def has_action? can?(user, :update_build, subject) end diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb new file mode 100644 index 00000000000..f2121210417 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Stop do + let(:core_status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(core_status) + end + + describe '#text' do + it { expect(subject.text).to eq 'stop' } + end + + describe '#label' do + it { expect(subject.label).to eq 'stop' } + end + + describe '#icon' do + it 'does not override core status icon' do + expect(core_status).to receive(:icon) + + subject.icon + end + end + + describe '.matches?' do + context 'build is playable' do + context 'when build stops an environment' do + let(:build) do + create(:ci_build, :playable, :teardown_environment) + end + + it 'is a correct match' do + expect(described_class.matches?(build, user)) + .to be true + end + end + + context 'when build does not stop an environment' do + let(:build) { create(:ci_build, :playable) } + + it 'does not match' do + expect(described_class.matches?(build, user)) + .to be false + end + end + end + + context 'when build is not playable' do + let(:build) { create(:ci_build) } + + it 'does not match' do + expect(described_class.matches?(build, user)) + .to be false + end + end + end +end -- cgit v1.2.1 From 48ea142819f206bb005184a050e812966cd23157 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 15:14:31 +0100 Subject: Fix pipeline specs for detailed statuses [ci skip] --- spec/models/ci/pipeline_spec.rb | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 8158e71dd55..e78ae14b737 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -442,11 +442,15 @@ describe Ci::Pipeline, models: true do end describe '#detailed_status' do + let(:user) { create(:user) } + + subject { pipeline.detailed_status(user) } + context 'when pipeline is created' do let(:pipeline) { create(:ci_pipeline, status: :created) } it 'returns detailed status for created pipeline' do - expect(pipeline.detailed_status.text).to eq 'created' + expect(subject.text).to eq 'created' end end @@ -454,7 +458,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :pending) } it 'returns detailed status for pending pipeline' do - expect(pipeline.detailed_status.text).to eq 'pending' + expect(subject.text).to eq 'pending' end end @@ -462,7 +466,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :running) } it 'returns detailed status for running pipeline' do - expect(pipeline.detailed_status.text).to eq 'running' + expect(subject.text).to eq 'running' end end @@ -470,7 +474,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :success) } it 'returns detailed status for successful pipeline' do - expect(pipeline.detailed_status.text).to eq 'passed' + expect(subject.text).to eq 'passed' end end @@ -478,7 +482,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :failed) } it 'returns detailed status for failed pipeline' do - expect(pipeline.detailed_status.text).to eq 'failed' + expect(subject.text).to eq 'failed' end end @@ -486,7 +490,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :canceled) } it 'returns detailed status for canceled pipeline' do - expect(pipeline.detailed_status.text).to eq 'canceled' + expect(subject.text).to eq 'canceled' end end @@ -494,7 +498,7 @@ describe Ci::Pipeline, models: true do let(:pipeline) { create(:ci_pipeline, status: :skipped) } it 'returns detailed status for skipped pipeline' do - expect(pipeline.detailed_status.text).to eq 'skipped' + expect(subject.text).to eq 'skipped' end end @@ -506,7 +510,7 @@ describe Ci::Pipeline, models: true do end it 'retruns detailed status for successful pipeline with warnings' do - expect(pipeline.detailed_status.label).to eq 'passed with warnings' + expect(subject.label).to eq 'passed with warnings' end end end -- cgit v1.2.1 From be13fc68e74cd6820a5b175365f72f12de339e57 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 15:17:18 +0100 Subject: Fix detailed status badge for generic commit status [ci skip] --- app/views/ci/status/_badge.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index b1b6e9c2b05..c00ddd183fb 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -5,4 +5,4 @@ - else %span{ class: "ci-status ci-#{status}" } = custom_icon(status.icon) - = detailed_status.text + = status.text -- cgit v1.2.1 From 211f019382c5e3fcf4812199aaf09b850eab09dd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 12 Dec 2016 16:30:24 +0200 Subject: Add index to routes table on lower path for postgresql Signed-off-by: Dmitriy Zaporozhets --- ...0161212142807_add_lower_path_index_to_routes.rb | 22 ++++++++++++++++++++++ db/schema.rb | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20161212142807_add_lower_path_index_to_routes.rb diff --git a/db/migrate/20161212142807_add_lower_path_index_to_routes.rb b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb new file mode 100644 index 00000000000..6958500306f --- /dev/null +++ b/db/migrate/20161212142807_add_lower_path_index_to_routes.rb @@ -0,0 +1,22 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddLowerPathIndexToRoutes < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + return unless Gitlab::Database.postgresql? + + execute 'CREATE INDEX CONCURRENTLY index_on_routes_lower_path ON routes (LOWER(path));' + end + + def down + return unless Gitlab::Database.postgresql? + + remove_index :routes, name: :index_on_routes_lower_path + end +end diff --git a/db/schema.rb b/db/schema.rb index 9c46f573719..ae47456084f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161202152035) do +ActiveRecord::Schema.define(version: 20161212142807) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 7d36c88916717646f583a95118a47a095ab449a0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 12 Dec 2016 15:20:21 +0100 Subject: Fix detailed status specs for pipeline stage model --- spec/models/ci/stage_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index f232761dba2..8fff38f7cda 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -68,7 +68,9 @@ describe Ci::Stage, models: true do end describe '#detailed_status' do - subject { stage.detailed_status } + let(:user) { create(:user) } + + subject { stage.detailed_status(user) } context 'when build is created' do let!(:stage_build) { create_job(:ci_build, status: :created) } -- cgit v1.2.1 From c1518a3230fb3f59111325c34fa5b13fa53e4767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 12 Dec 2016 18:17:21 +0100 Subject: Don't require `API::API` in routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `API::API` is autoloaded and shouldn't be required in non-autoloaded code. Otherwise, we get the following dreaded error: A copy of API::Helpers has been removed from the module tree but is still active! Signed-off-by: Rémy Coutable --- config/routes.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 03b47261e7e..06d565df469 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,5 @@ require 'sidekiq/web' require 'sidekiq/cron/web' -require 'api/api' require 'constraints/group_url_constrainer' Rails.application.routes.draw do -- cgit v1.2.1 From ac00009fccec66a768ae9dffff4b453ad2a6b13f Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Thu, 8 Dec 2016 18:32:08 -0200 Subject: Allow to delete tag release note --- app/controllers/projects/releases_controller.rb | 9 +++- changelogs/unreleased/issue_13270.yml | 4 ++ .../projects/releases_controller_spec.rb | 55 ++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/issue_13270.yml create mode 100644 spec/controllers/projects/releases_controller_spec.rb diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 0825a4311cb..2c097cb4d8d 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -10,7 +10,14 @@ class Projects::ReleasesController < Projects::ApplicationController end def update - release.update_attributes(release_params) + # Release belongs to Tag which is not active record object, + # it exists only to save a description to each Tag. + # If description is empty we should destroy the existing record. + if release_params[:description].present? + release.update_attributes(release_params) + else + release.destroy + end redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) end diff --git a/changelogs/unreleased/issue_13270.yml b/changelogs/unreleased/issue_13270.yml new file mode 100644 index 00000000000..9c15c436876 --- /dev/null +++ b/changelogs/unreleased/issue_13270.yml @@ -0,0 +1,4 @@ +--- +title: Allow to delete tag release note +merge_request: +author: diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb new file mode 100644 index 00000000000..9fd5c3b85f6 --- /dev/null +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Projects::ReleasesController do + let!(:project) { create(:project) } + let!(:user) { create(:user) } + let!(:release) { create(:release, project: project) } + let!(:tag) { release.tag } + + before do + project.team << [user, :developer] + sign_in(user) + end + + describe 'GET #edit' do + it 'initializes a new release' do + tag_id = release.tag + project.releases.destroy_all + + get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: tag_id + + release = assigns(:release) + expect(release).not_to be_nil + expect(release).not_to be_persisted + end + + it 'retrieves an existing release' do + get :edit, namespace_id: project.namespace.path, project_id: project.path, tag_id: release.tag + + release = assigns(:release) + expect(release).not_to be_nil + expect(release).to be_persisted + end + end + + describe 'PUT #update' do + it 'updates release note description' do + update_release('description updated') + + release = project.releases.find_by_tag(tag) + expect(release.description).to eq("description updated") + end + + it 'deletes release note when description is null' do + expect { update_release('') }.to change(project.releases, :count).by(-1) + end + end + + def update_release(description) + put :update, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + tag_id: release.tag, + release: { description: description } + end +end -- cgit v1.2.1 From e8d1c7a5b6b942f591539531bfe48dc72b3cd16b Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Mon, 12 Dec 2016 13:03:22 -0600 Subject: break comment "edited at" block out of the note-text element to prevent issues with inline text nodes --- app/views/projects/notes/_note.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index ba8895438c5..778a32e6345 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -65,7 +65,7 @@ .note-text.md = preserve do = note.redacted_note_html - = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) + = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note_editable = render 'projects/notes/edit_form', note: note .note-awards -- cgit v1.2.1 From 98d42e9f85b0636632b77a67d1a20d64eb6ed22a Mon Sep 17 00:00:00 2001 From: Matt Lee Date: Fri, 9 Dec 2016 21:21:29 +0000 Subject: 8.14 requires GitLab shell 4.0.3 --- doc/update/8.13-to-8.14.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.13-to-8.14.md b/doc/update/8.13-to-8.14.md index a0e895773ce..c64d3407461 100644 --- a/doc/update/8.13-to-8.14.md +++ b/doc/update/8.13-to-8.14.md @@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-14-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v4.0.0 +sudo -u git -H git checkout v4.0.3 ``` ### 6. Update gitlab-workhorse -- cgit v1.2.1 From 569645a809d042fb7a69d5c95ad65fe6983c2f75 Mon Sep 17 00:00:00 2001 From: victorwu Date: Fri, 9 Dec 2016 15:48:23 -0600 Subject: Clean up commit copy to clipboard and make consistent --- app/assets/javascripts/copy_to_clipboard.js | 4 ++-- app/helpers/button_helper.rb | 2 +- app/views/projects/commit/_commit_box.html.haml | 9 ++------- app/views/projects/tree/_tree_content.html.haml | 6 +----- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index 1cc34e490c2..efa228a75d9 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -6,7 +6,7 @@ var genericError, genericSuccess, showTooltip; genericSuccess = function(e) { - showTooltip(e.trigger, 'Copied!'); + showTooltip(e.trigger, 'Copied'); // Clear the selection and blur the trigger so it loses its border e.clearSelection(); return $(e.trigger).blur(); @@ -31,7 +31,7 @@ var originalTitle = $target.data('original-title'); $target - .attr('title', 'Copied!') + .attr('title', 'Copied') .tooltip('fixTitle') .tooltip('show') .attr('title', originalTitle) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index dee3c78df47..4c7c16d694c 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -16,7 +16,7 @@ module ButtonHelper # See http://clipboardjs.com/#usage def clipboard_button(data = {}) css_class = data[:class] || 'btn-clipboard btn-transparent' - title = data[:title] || 'Copy to Clipboard' + title = data[:title] || 'Copy to clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 65151ac3a56..c08ed8f6c16 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,13 +1,8 @@ .page-content-header .header-main-content - %strong Commit - %strong.monospace.js-details-short= @commit.short_id - = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do - %span.text-expander - \... - %span.js-details-content.hide - %strong.monospace.commit-hash-full= @commit.id + %strong = clipboard_button(clipboard_text: @commit.id) + = @commit.short_id %span.hidden-xs authored #{time_ago_with_tooltip(@commit.authored_date)} %span by diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 21e378b8735..dc7b4bab208 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -5,14 +5,10 @@ %tr %th Name %th.hidden-xs - .pull-left Last Commit .last-commit.hidden-sm.pull-left -   - %i.fa.fa-angle-right -   %small.light + = clipboard_button(clipboard_text: @commit.id) = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" - – = time_ago_with_tooltip(@commit.committed_date) = @commit.full_title %small.commit-history-link-spacer | -- cgit v1.2.1 From a6ca5689b0df3a020a9cf9e7f9b9ed1a76863ec1 Mon Sep 17 00:00:00 2001 From: victorwu Date: Fri, 9 Dec 2016 16:48:53 -0600 Subject: Tweak style and add back wording --- app/views/projects/tree/_tree_content.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index dc7b4bab208..d37c376c36b 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -5,8 +5,9 @@ %tr %th Name %th.hidden-xs + .pull-left Last commit .last-commit.hidden-sm.pull-left - %small.light + %small.light = clipboard_button(clipboard_text: @commit.id) = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" = time_ago_with_tooltip(@commit.committed_date) -- cgit v1.2.1 From 602447a399b831c16023af9701db60e38931e77d Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Sat, 10 Dec 2016 00:55:16 +0000 Subject: Fix test --- spec/views/projects/commit/_commit_box.html.haml_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb index 16bf0698c4b..e741e3cf9b6 100644 --- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb +++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb @@ -13,7 +13,7 @@ describe 'projects/commit/_commit_box.html.haml' do it 'shows the commit SHA' do render - expect(rendered).to have_text("Commit #{Commit.truncate_sha(project.commit.sha)}") + expect(rendered).to have_text("#{Commit.truncate_sha(project.commit.sha)}") end it 'shows the last pipeline that ran for the commit' do -- cgit v1.2.1 From 53dc398bd608dc0e1c731aa62e275197b6a2345d Mon Sep 17 00:00:00 2001 From: Jon Bailey Date: Mon, 12 Dec 2016 20:31:04 +0000 Subject: Crontab typo '* */6' -> '0 */6' (4x/day not 1x-per-min-for-1h 4x/day) --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 9ddd1554811..0ee1b1ec634 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -302,7 +302,7 @@ Settings.cron_jobs['remove_expired_group_links_worker'] ||= Settingslogic.new({} Settings.cron_jobs['remove_expired_group_links_worker']['cron'] ||= '10 0 * * *' Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveExpiredGroupLinksWorker' Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *' +Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 */6 * * *' Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker' Settings.cron_jobs['trending_projects_worker'] ||= Settingslogic.new({}) -- cgit v1.2.1 From f21e13ae194cc6669100b663d757124612fe7c28 Mon Sep 17 00:00:00 2001 From: winniehell Date: Tue, 22 Nov 2016 13:49:06 +0100 Subject: Replace static fixture for awards_handler_spec (!7661) --- changelogs/unreleased/awards_handler.yml | 4 ++ spec/javascripts/awards_handler_spec.js | 10 ++--- spec/javascripts/fixtures/awards_handler.html.haml | 52 ---------------------- 3 files changed, 9 insertions(+), 57 deletions(-) create mode 100644 changelogs/unreleased/awards_handler.yml delete mode 100644 spec/javascripts/fixtures/awards_handler.html.haml diff --git a/changelogs/unreleased/awards_handler.yml b/changelogs/unreleased/awards_handler.yml new file mode 100644 index 00000000000..1f9904c0691 --- /dev/null +++ b/changelogs/unreleased/awards_handler.yml @@ -0,0 +1,4 @@ +--- +title: Replace static fixture for awards_handler_spec +merge_request: 7661 +author: winniehell diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index ac1404f6e1c..7b2e3db6218 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -33,9 +33,9 @@ }; describe('AwardsHandler', function() { - fixture.preload('awards_handler.html'); + fixture.preload('issues/open-issue.html.raw'); beforeEach(function() { - fixture.load('awards_handler.html'); + fixture.load('issues/open-issue.html.raw'); awardsHandler = new AwardsHandler; spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) { return function(url, emoji, cb) { @@ -113,7 +113,7 @@ }); describe('::getAwardUrl', function() { return it('should return the url for request', function() { - return expect(awardsHandler.getAwardUrl()).toBe('/gitlab-org/gitlab-test/issues/8/toggle_award_emoji'); + return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji'); }); }); describe('::addAward and ::checkMutuality', function() { @@ -209,7 +209,7 @@ $('.js-add-award').eq(0).click(); $menu = $('.emoji-menu'); $block = $('.js-awards-block'); - $emoji = $menu.find(".emoji-menu-list-item " + selector); + $emoji = $menu.find('.emoji-menu-list:not(.frequent-emojis) ' + selector); expect($emoji.length).toBe(1); expect($block.find(selector).length).toBe(0); $emoji.click(); @@ -224,7 +224,7 @@ openEmojiMenuAndAddEmoji(); $('.js-add-award').eq(0).click(); $block = $('.js-awards-block'); - $emoji = $('.emoji-menu').find(".emoji-menu-list-item " + selector); + $emoji = $('.emoji-menu').find(".emoji-menu-list:not(.frequent-emojis) " + selector); $emoji.click(); return expect($block.find(selector).length).toBe(0); }); diff --git a/spec/javascripts/fixtures/awards_handler.html.haml b/spec/javascripts/fixtures/awards_handler.html.haml deleted file mode 100644 index 1ef2e8f8624..00000000000 --- a/spec/javascripts/fixtures/awards_handler.html.haml +++ /dev/null @@ -1,52 +0,0 @@ -.issue-details.issuable-details - .detail-page-description.content-block - %h2.title Quibusdam sint officiis earum molestiae ipsa autem voluptatem nisi rem. - .description.js-task-list-container.is-task-list-enabled - .wiki - %p Qui exercitationem magnam optio quae fuga earum odio. - %textarea.hidden.js-task-list-field Qui exercitationem magnam optio quae fuga earum odio. - %small.edited-text - .content-block.content-block-small - .awards.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/issues/8/toggle_award_emoji"} - %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"} - .icon.emoji-icon.emoji-1F44D{"data-aliases" => "", "data-emoji" => "thumbsup", "data-unicode-name" => "1F44D", :title => "thumbsup"} - %span.award-control-text.js-counter 0 - %button.award-control.btn.js-emoji-btn{"data-placement" => "bottom", "data-title" => "", :type => "button"} - .icon.emoji-icon.emoji-1F44E{"data-aliases" => "", "data-emoji" => "thumbsdown", "data-unicode-name" => "1F44E", :title => "thumbsdown"} - %span.award-control-text.js-counter 0 - .award-menu-holder.js-award-holder - %button.btn.award-control.js-add-award{:type => "button"} - %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal - %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading - %span.award-control-text Add - %section.issuable-discussion - #notes - %ul#notes-list.notes.main-notes-list.timeline - %li#note_348.note.note-row-348.timeline-entry{"data-author-id" => "18", "data-editable" => ""} - .timeline-entry-inner - .timeline-icon - %a{:href => "/u/agustin"} - %img.avatar.s40{:alt => "", :src => "#"}/ - .timeline-content - .note-header - %a.author_link{:href => "/u/agustin"} - %span.author Brenna Stokes - .inline.note-headline-light - @agustin commented - %a{:href => "#note_348"} - %time 11 days ago - .note-actions - %span.note-role Reporter - %a.note-action-button.note-emoji-button.js-add-award.js-note-emoji{"data-position" => "right", :href => "#", :title => "Award Emoji"} - %i.fa.fa-spinner.fa-spin - %i.fa.fa-smile-o.link-highlight - .js-task-list-container.note-body.is-task-list-enabled - .note-text - %p Suscipit sunt quia quisquam sed eveniet ipsam. - .note-awards - .awards.hidden.js-awards-block{"data-award-url" => "/gitlab-org/gitlab-test/notes/348/toggle_award_emoji"} - .award-menu-holder.js-award-holder - %button.btn.award-control.js-add-award{:type => "button"} - %i.fa.fa-smile-o.award-control-icon.award-control-icon-normal - %i.fa.fa-spinner.fa-spin.award-control-icon.award-control-icon-loading - %span.award-control-text Add -- cgit v1.2.1 From 2cbd07e69c647726cfb927bf35a594850d4f22fa Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 30 Nov 2016 17:12:43 +0100 Subject: Don't allow blank MR titles in API --- lib/api/merge_requests.rb | 4 ++-- spec/requests/api/merge_requests_spec.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 55bdbc6a47c..5d1fe22f2df 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -143,8 +143,8 @@ module API success Entities::MergeRequest end params do - optional :title, type: String, desc: 'The title of the merge request' - optional :target_branch, type: String, desc: 'The target branch' + optional :title, type: String, allow_blank: false, desc: 'The title of the merge request' + optional :target_branch, type: String, allow_blank: false, desc: 'The target branch' optional :state_event, type: String, values: %w[close reopen merge], desc: 'Status of the merge request' use :optional_params diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 75b270aa93c..f032d1b683d 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -533,6 +533,22 @@ describe API::MergeRequests, api: true do expect(json_response['labels']).to include '?' expect(json_response['labels']).to include '&' end + + it 'does not update state when title is empty' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', title: nil + + merge_request.reload + expect(response).to have_http_status(400) + expect(merge_request.state).to eq('opened') + end + + it 'does not update state when target_branch is empty' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user), state_event: 'close', target_branch: nil + + merge_request.reload + expect(response).to have_http_status(400) + expect(merge_request.state).to eq('opened') + end end describe "POST /projects/:id/merge_requests/:merge_request_id/comments" do -- cgit v1.2.1 From c16b24af04643edc598b20b338eff25e463ed29b Mon Sep 17 00:00:00 2001 From: winniehell Date: Tue, 13 Dec 2016 02:10:30 +0100 Subject: Add failing test for #20190 --- .../projects/files/creating_a_file_spec.rb | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 spec/features/projects/files/creating_a_file_spec.rb diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb new file mode 100644 index 00000000000..ae448706130 --- /dev/null +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +feature 'User wants to create a file', feature: true do + include WaitForAjax + + let(:project) { create(:project) } + let(:user) { create(:user) } + + background do + project.team << [user, :master] + login_as user + visit namespace_project_new_blob_path(project.namespace, project, project.default_branch) + end + + def submit_new_file(options) + file_name = find('#file_name') + file_name.set options[:file_name] || 'README.md' + + file_content = find('#file-content') + file_content.set options[:file_content] || 'Some content' + + click_button 'Commit Changes' + end + + scenario 'file name contains Chinese characters' do + submit_new_file(file_name: '测试.md') + expect(page).to have_content 'The file has been successfully created.' + end + + scenario 'directory name contains Chinese characters' do + submit_new_file(file_name: '中文/测试.md') + expect(page).to have_content 'The file has been successfully created.' + end + + scenario 'file name contains invalid characters' do + submit_new_file(file_name: '\\') + expect(page).to have_content 'Your changes could not be committed, because the file name can contain only' + end + + scenario 'file name contains directory traversal' do + submit_new_file(file_name: '../README.md') + expect(page).to have_content 'Your changes could not be committed, because the file name cannot include directory traversal.' + end +end -- cgit v1.2.1 From 61aa90ef2089c9d840b88f14a553ef0dd49b779f Mon Sep 17 00:00:00 2001 From: winniehell Date: Thu, 8 Dec 2016 22:53:15 +0100 Subject: Allow all alphanumeric characters in file names (!8002) --- changelogs/unreleased/allow-more-filenames.yml | 4 ++++ lib/gitlab/regex.rb | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/allow-more-filenames.yml diff --git a/changelogs/unreleased/allow-more-filenames.yml b/changelogs/unreleased/allow-more-filenames.yml new file mode 100644 index 00000000000..7989f94e528 --- /dev/null +++ b/changelogs/unreleased/allow-more-filenames.yml @@ -0,0 +1,4 @@ +--- +title: Allow all alphanumeric characters in file names +merge_request: 8002 +author: winniehell diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index a06cf6a989c..d9d1e3cccca 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -61,7 +61,7 @@ module Gitlab end def file_name_regex - @file_name_regex ||= /\A[a-zA-Z0-9_\-\.\@]*\z/.freeze + @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@]*\z/.freeze end def file_name_regex_message @@ -69,7 +69,7 @@ module Gitlab end def file_path_regex - @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/\@]*\z/.freeze + @file_path_regex ||= /\A[[[:alnum:]]_\-\.\/\@]*\z/.freeze end def file_path_regex_message -- cgit v1.2.1 From 605715067767572ff96964370d78e7b31083ddde Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 28 Nov 2016 11:34:02 +0000 Subject: Adds manual action icon and case to show it --- app/helpers/ci_status_helper.rb | 2 ++ app/views/shared/icons/_icon_status_manual.svg | 1 + 2 files changed, 3 insertions(+) create mode 100755 app/views/shared/icons/_icon_status_manual.svg diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index d9f5e01f0dc..eb2aeaa4628 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -48,6 +48,8 @@ module CiStatusHelper 'icon_status_created' when 'skipped' 'icon_status_skipped' + when 'manual' + 'icon_status_manual' else 'icon_status_canceled' end diff --git a/app/views/shared/icons/_icon_status_manual.svg b/app/views/shared/icons/_icon_status_manual.svg new file mode 100755 index 00000000000..ffb00c6f443 --- /dev/null +++ b/app/views/shared/icons/_icon_status_manual.svg @@ -0,0 +1 @@ + -- cgit v1.2.1 From 2011f8f1c2b4788df04fd76dcab816ab337e9e08 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 11:53:36 +0100 Subject: Use manual build icon in play/stop build statuses --- app/helpers/ci_status_helper.rb | 2 -- lib/gitlab/ci/status/build/play.rb | 8 ++++++-- lib/gitlab/ci/status/build/stop.rb | 8 ++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index eb2aeaa4628..d9f5e01f0dc 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -48,8 +48,6 @@ module CiStatusHelper 'icon_status_created' when 'skipped' 'icon_status_skipped' - when 'manual' - 'icon_status_manual' else 'icon_status_canceled' end diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index e3066d40a37..974b9e9b1f9 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -6,11 +6,15 @@ module Gitlab include Status::Extended def text - 'play' + 'manual' end def label - 'play' + 'manual play action' + end + + def icon + 'icon_status_manual' end def has_action? diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index a4966a55892..f8ffa95cde4 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -6,11 +6,15 @@ module Gitlab include Status::Extended def text - 'stop' + 'manual' end def label - 'stop' + 'manual stop action' + end + + def icon + 'icon_status_manual' end def has_action? -- cgit v1.2.1 From 48d43608b8e9118033c1701c98f330aa10f9eb54 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 12:27:01 +0100 Subject: Refine build stop/play extended status specs --- lib/gitlab/ci/status/build/play.rb | 8 +++---- spec/lib/gitlab/ci/status/build/play_spec.rb | 27 ++++++++------------- spec/lib/gitlab/ci/status/build/stop_spec.rb | 35 +++++++++++++++++----------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index 974b9e9b1f9..5c506e6d59f 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -21,14 +21,14 @@ module Gitlab can?(user, :update_build, subject) end - def action_title - 'Play' - end - def action_icon 'play' end + def action_title + 'Play' + end + def action_class 'ci-play-icon' end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index ae103d8993d..180a2808a42 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -1,30 +1,26 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Play do - let(:core_status) { double('core status') } + let(:status) { double('core') } let(:user) { double('user') } - subject do - described_class.new(core_status) - end + subject { described_class.new(status) } describe '#text' do - it { expect(subject.text).to eq 'play' } + it { expect(subject.text).to eq 'manual' } end describe '#label' do - it { expect(subject.label).to eq 'play' } + it { expect(subject.label).to eq 'manual play action' } end describe '#icon' do - it 'does not override core status icon' do - expect(core_status).to receive(:icon) - - subject.icon - end + it { expect(subject.icon).to eq 'icon_status_manual' } end describe '.matches?' do + subject { described_class.matches?(build, user) } + context 'build is playable' do context 'when build stops an environment' do let(:build) do @@ -32,8 +28,7 @@ describe Gitlab::Ci::Status::Build::Play do end it 'does not match' do - expect(described_class.matches?(build, user)) - .to be false + expect(subject).to be false end end @@ -41,8 +36,7 @@ describe Gitlab::Ci::Status::Build::Play do let(:build) { create(:ci_build, :playable) } it 'is a correct match' do - expect(described_class.matches?(build, user)) - .to be true + expect(subject).to be true end end end @@ -51,8 +45,7 @@ describe Gitlab::Ci::Status::Build::Play do let(:build) { create(:ci_build) } it 'does not match' do - expect(described_class.matches?(build, user)) - .to be false + expect(subject).to be false end end end diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index f2121210417..bc1939fb493 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -1,30 +1,40 @@ require 'spec_helper' describe Gitlab::Ci::Status::Build::Stop do - let(:core_status) { double('core status') } + let(:status) { double('core status') } let(:user) { double('user') } subject do - described_class.new(core_status) + described_class.new(status) end describe '#text' do - it { expect(subject.text).to eq 'stop' } + it { expect(subject.text).to eq 'manual' } end describe '#label' do - it { expect(subject.label).to eq 'stop' } + it { expect(subject.label).to eq 'manual stop action' } end describe '#icon' do - it 'does not override core status icon' do - expect(core_status).to receive(:icon) + it { expect(subject.icon).to eq 'icon_status_manual' } + end - subject.icon - end + describe '#has_action?' do + end + + describe '#action_icon' do + end + + describe '#action_path' do + end + + describe '#action_title' do end describe '.matches?' do + subject { described_class.matches?(build, user) } + context 'build is playable' do context 'when build stops an environment' do let(:build) do @@ -32,8 +42,7 @@ describe Gitlab::Ci::Status::Build::Stop do end it 'is a correct match' do - expect(described_class.matches?(build, user)) - .to be true + expect(subject).to be true end end @@ -41,8 +50,7 @@ describe Gitlab::Ci::Status::Build::Stop do let(:build) { create(:ci_build, :playable) } it 'does not match' do - expect(described_class.matches?(build, user)) - .to be false + expect(subject).to be false end end end @@ -51,8 +59,7 @@ describe Gitlab::Ci::Status::Build::Stop do let(:build) { create(:ci_build) } it 'does not match' do - expect(described_class.matches?(build, user)) - .to be false + expect(subject).to be false end end end -- cgit v1.2.1 From 9d097ebdb5396ad6fdb93301417fc7de5f71eff7 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Tue, 13 Dec 2016 01:28:57 +0100 Subject: added border-radius and padding to labels --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/issuable.scss | 1 + app/assets/stylesheets/pages/labels.scss | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 18716813c48..a1d5f6427f4 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -486,6 +486,7 @@ $jq-ui-default-color: #777; $label-gray-bg: #f8fafc; $label-inverse-bg: #333; $label-remove-border: rgba(0, 0, 0, .1); +$label-border-radius: 14px; /* * Lint diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 90587b9425b..407c0afbac8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -30,6 +30,7 @@ .color-label { padding: 6px 10px; + border-radius: $label-border-radius; } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index b1ccd644450..25c91203ff4 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -104,7 +104,8 @@ } .color-label { - padding: 3px 4px; + padding: 3px 7px; + border-radius: $label-border-radius; } .dropdown-labels-error { -- cgit v1.2.1 From 65c3bfe68a3a0599187219ba49a1a46f23b45312 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 13:13:09 +0100 Subject: Extend specs for build play/stop detailed statuses --- spec/lib/gitlab/ci/status/build/play_spec.rb | 30 ++++++++++++++++++++++++++ spec/lib/gitlab/ci/status/build/stop_spec.rb | 32 ++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 180a2808a42..0fa4405c604 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -18,6 +18,36 @@ describe Gitlab::Ci::Status::Build::Play do it { expect(subject.icon).to eq 'icon_status_manual' } end + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } + + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/play" } + end + + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'play' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Play' } + end + end + describe '.matches?' do subject { described_class.matches?(build, user) } diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index bc1939fb493..984cd1e5007 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -20,16 +20,34 @@ describe Gitlab::Ci::Status::Build::Stop do it { expect(subject.icon).to eq 'icon_status_manual' } end - describe '#has_action?' do - end + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } - describe '#action_icon' do - end + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } - describe '#action_path' do - end + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/play" } + end - describe '#action_title' do + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'stop' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Stop' } + end end describe '.matches?' do -- cgit v1.2.1 From a91a95b2327a3570db16afa1fde91c899d79d5a5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 13:22:40 +0100 Subject: Add tests for build cancelable/retryable statuses --- spec/lib/gitlab/ci/status/build/cancelable_spec.rb | 86 ++++++++++++++++++++++ spec/lib/gitlab/ci/status/build/retryable_spec.rb | 86 ++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 spec/lib/gitlab/ci/status/build/cancelable_spec.rb create mode 100644 spec/lib/gitlab/ci/status/build/retryable_spec.rb diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb new file mode 100644 index 00000000000..989328c0719 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Cancelable do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'does not override status icon' do + expect(status).to receive(:icon) + + subject.icon + end + end + + describe '#label' do + it 'does not override status label' do + expect(status).to receive(:label) + + subject.label + end + end + + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } + + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/cancel" } + end + + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'ban' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Cancel' } + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'build is cancelable' do + let(:build) do + create(:ci_build, :running) + end + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not cancelable' do + let(:build) { create(:ci_build, :success) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb new file mode 100644 index 00000000000..59f6b78f99d --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Retryable do + let(:status) { double('core status') } + let(:user) { double('user') } + + subject do + described_class.new(status) + end + + describe '#text' do + it 'does not override status text' do + expect(status).to receive(:text) + + subject.text + end + end + + describe '#icon' do + it 'does not override status icon' do + expect(status).to receive(:icon) + + subject.icon + end + end + + describe '#label' do + it 'does not override status label' do + expect(status).to receive(:label) + + subject.label + end + end + + describe 'action details' do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:status) { Gitlab::Ci::Status::Core.new(build, user) } + + describe '#has_action?' do + context 'when user is allowed to update build' do + before { build.project.team << [user, :developer] } + + it { is_expected.to have_action } + end + + context 'when user is not allowed to update build' do + it { is_expected.not_to have_action } + end + end + + describe '#action_path' do + it { expect(subject.action_path).to include "#{build.id}/retry" } + end + + describe '#action_icon' do + it { expect(subject.action_icon).to eq 'refresh' } + end + + describe '#action_title' do + it { expect(subject.action_title).to eq 'Retry' } + end + end + + describe '.matches?' do + subject { described_class.matches?(build, user) } + + context 'build is retryable' do + let(:build) do + create(:ci_build, :success) + end + + it 'is a correct match' do + expect(subject).to be true + end + end + + context 'when build is not retryable' do + let(:build) { create(:ci_build, :running) } + + it 'does not match' do + expect(subject).to be false + end + end + end +end -- cgit v1.2.1 From 5f590a71fd26b637501f8751bc6f9adff4d9c8db Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 13:24:25 +0100 Subject: Improve readability in methods for detailed status --- app/models/ci/build.rb | 4 +++- app/models/ci/pipeline.rb | 4 +++- app/models/ci/stage.rb | 4 +++- app/models/commit_status.rb | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 65ee327a8e5..63a4a075f8e 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -101,7 +101,9 @@ module Ci end def detailed_status(current_user) - Gitlab::Ci::Status::Build::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Build::Factory + .new(self, current_user) + .fabricate! end def manual? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 1f33106d358..54f73171fd4 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -337,7 +337,9 @@ module Ci end def detailed_status(current_user) - Gitlab::Ci::Status::Pipeline::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Pipeline::Factory + .new(self, current_user) + .fabricate! end private diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index be52cce20f1..7ef59445d77 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -23,7 +23,9 @@ module Ci end def detailed_status(current_user) - Gitlab::Ci::Status::Stage::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Stage::Factory + .new(self, current_user) + .fabricate! end def statuses diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 6548a7dda2c..31cd381dcd2 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -133,6 +133,8 @@ class CommitStatus < ActiveRecord::Base end def detailed_status(current_user) - Gitlab::Ci::Status::Factory.new(self, current_user).fabricate! + Gitlab::Ci::Status::Factory + .new(self, current_user) + .fabricate! end end -- cgit v1.2.1 From 549242c620a812b4ed0f1a3b08f3e315027b9e65 Mon Sep 17 00:00:00 2001 From: Julian Zinn Date: Fri, 4 Nov 2016 12:36:37 -0700 Subject: For single line git commit messages, the close quote should be on the same line as the open quote MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/project_services/jira_service.rb | 2 +- changelogs/unreleased/chomp-git-status-message.yml | 5 +++++ spec/services/system_note_service_spec.rb | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/chomp-git-status-message.yml diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 894315a8593..2d969d2fcb6 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -220,7 +220,7 @@ class JiraService < IssueTrackerService entity_title = data[:entity][:title] project_name = data[:project][:name] - message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'" + message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'" link_title = "GitLab: Mentioned on #{entity_name} - #{entity_title}" link_props = build_remote_link_props(url: entity_url, title: link_title) diff --git a/changelogs/unreleased/chomp-git-status-message.yml b/changelogs/unreleased/chomp-git-status-message.yml new file mode 100644 index 00000000000..f70607df7a1 --- /dev/null +++ b/changelogs/unreleased/chomp-git-status-message.yml @@ -0,0 +1,5 @@ +--- +title: For single line git commit messages, the close quote should be on the same + line as the open quote +merge_request: +author: diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 90b7e62bc6f..0e8adb68721 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -694,7 +694,7 @@ describe SystemNoteService, services: true do describe "existing reference" do before do - message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title}'" + message = "[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\n'#{commit.title.chomp}'" allow_any_instance_of(JIRA::Resource::Issue).to receive(:comments).and_return([OpenStruct.new(body: message)]) end -- cgit v1.2.1 From ec25abe6255a1cc0a4c68192894cc7ac1cb90f54 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 13 Dec 2016 14:48:02 +0200 Subject: Add AddLowerPathIndexToRoutes to setup_postgresql.rake Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/migrate/setup_postgresql.rake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index 141a0b74ec0..f5caca3ddbf 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -1,8 +1,12 @@ +require Rails.root.join('lib/gitlab/database') +require Rails.root.join('lib/gitlab/database/migration_helpers') require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes') require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes') +require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes') desc 'GitLab | Sets up PostgreSQL' task setup_postgresql: :environment do NamespacesProjectsPathLowerIndexes.new.up AddUsersLowerUsernameEmailIndexes.new.up + AddLowerPathIndexToRoutes.new.up end -- cgit v1.2.1 From 7841be243e5f28828cea95500b554d7f5cc039eb Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 13 Dec 2016 13:51:30 +0100 Subject: API: Ability to get group's project in simple representation --- changelogs/unreleased/api-simple-group-project.yml | 4 ++++ doc/api/groups.md | 17 +++++++++++------ lib/api/groups.rb | 5 ++++- spec/requests/api/groups_spec.rb | 15 +++++++++++++-- 4 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/api-simple-group-project.yml diff --git a/changelogs/unreleased/api-simple-group-project.yml b/changelogs/unreleased/api-simple-group-project.yml new file mode 100644 index 00000000000..54c8de610a6 --- /dev/null +++ b/changelogs/unreleased/api-simple-group-project.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Simple representation of group''s projects' +merge_request: 8060 +author: Robert Schilling diff --git a/doc/api/groups.md b/doc/api/groups.md index 5e6f498c365..134d7bda22f 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -50,12 +50,17 @@ GET /groups/:id/projects Parameters: -- `archived` (optional) - if passed, limit by archived status -- `visibility` (optional) - if passed, limit by visibility `public`, `internal`, `private` -- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` -- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` -- `search` (optional) - Return list of authorized projects according to a search criteria -- `ci_enabled_first` - Return projects ordered by ci_enabled flag. Projects with enabled GitLab CI go first +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or path of a group | +| `archived` | boolean | no | Limit by archived status | +| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private` | +| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | +| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | +| `search` | string | no | Return list of authorized projects matching the search criteria | +| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | + +Example response: ```json [ diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 105d3ee342e..9b9d3df7435 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -125,13 +125,16 @@ module API default: 'created_at', desc: 'Return projects ordered by field' optional :sort, type: String, values: %w[asc desc], default: 'desc', desc: 'Return projects sorted in ascending and descending order' + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' use :pagination end get ":id/projects" do group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) projects = filter_projects(projects) - present paginate(projects), with: Entities::Project, user: current_user + entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project + present paginate(projects), with: entity, user: current_user end desc 'Transfer a project to the group namespace. Available only for admin.' do diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index 15647b262b6..a75ba824e85 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -243,17 +243,28 @@ describe API::Groups, api: true do expect(json_response.length).to eq(2) project_names = json_response.map { |proj| proj['name' ] } expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['default_branch']).to be_present + end + + it "returns the group's projects with simple representation" do + get api("/groups/#{group1.id}/projects", user1), simple: true + + expect(response).to have_http_status(200) + expect(json_response.length).to eq(2) + project_names = json_response.map { |proj| proj['name' ] } + expect(project_names).to match_array([project1.name, project3.name]) + expect(json_response.first['default_branch']).not_to be_present end it 'filters the groups projects' do - public_projet = create(:project, :public, path: 'test1', group: group1) + public_project = create(:project, :public, path: 'test1', group: group1) get api("/groups/#{group1.id}/projects", user1), visibility: 'public' expect(response).to have_http_status(200) expect(json_response).to be_an(Array) expect(json_response.length).to eq(1) - expect(json_response.first['name']).to eq(public_projet.name) + expect(json_response.first['name']).to eq(public_project.name) end it "does not return a non existing group" do -- cgit v1.2.1 From a9ec4ec07e64d5f823c30d9bcc4c4bb1be7f2d75 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 13:57:18 +0100 Subject: Make build retryable only if complete and executed --- app/models/ci/build.rb | 3 ++- spec/models/build_spec.rb | 38 ++++++++++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 63a4a075f8e..89b0cae25ed 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -134,7 +134,8 @@ module Ci end def retryable? - project.builds_enabled? && commands.present? && complete? + project.builds_enabled? && commands.present? && + (success? || failed?) end def retried? diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index d4970e38f7c..2b678355ee5 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -900,20 +900,42 @@ describe Ci::Build, models: true do end describe '#retryable?' do - context 'when build is running' do - before do - build.run! + subject { build } + + context 'when build is retryable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.to be_retryable } end - it { expect(build).not_to be_retryable } + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.to be_retryable } + end end - context 'when build is finished' do - before do - build.success! + context 'when build is not retryable' do + context 'when build is running' do + before do + build.run! + end + + it { is_expected.not_to be_retryable } end - it { expect(build).to be_retryable } + context 'when build is skipped' do + before do + build.skip! + end + + it { is_expected.not_to be_retryable } + end end end -- cgit v1.2.1 From f4513d5c1981922e71a00b9e4b135605eb674c6f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:01:17 +0100 Subject: Make it possible to retry build that was canceled --- app/models/ci/build.rb | 2 +- spec/models/build_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 89b0cae25ed..e7cf606a7ae 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -135,7 +135,7 @@ module Ci def retryable? project.builds_enabled? && commands.present? && - (success? || failed?) + (success? || failed? || canceled?) end def retried? diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 2b678355ee5..e9b4cac5fef 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -918,6 +918,14 @@ describe Ci::Build, models: true do it { is_expected.to be_retryable } end + + context 'when build is canceled' do + before do + build.cancel! + end + + it { is_expected.to be_retryable } + end end context 'when build is not retryable' do -- cgit v1.2.1 From d7a774320e9e79bebb57dbbb927ff6af6d09e0e2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:07:51 +0100 Subject: Add tests for detailed build statuses factory --- spec/lib/gitlab/ci/status/build/factory_spec.rb | 141 ++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 spec/lib/gitlab/ci/status/build/factory_spec.rb diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb new file mode 100644 index 00000000000..dccb29b5ef6 --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb @@ -0,0 +1,141 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Factory do + let(:user) { create(:user) } + let(:project) { build.project } + + subject { described_class.new(build, user) } + let(:status) { subject.fabricate! } + + before { project.team << [user, :developer] } + + context 'when build is successful' do + let(:build) { create(:ci_build, :success) } + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'passed' + expect(status.icon).to eq 'icon_status_success' + expect(status.label).to eq 'passed' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is failed' do + let(:build) { create(:ci_build, :failed) } + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'failed' + expect(status.icon).to eq 'icon_status_failed' + expect(status.label).to eq 'failed' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is a canceled' do + let(:build) { create(:ci_build, :canceled) } + + it 'fabricates a retryable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Retryable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'canceled' + expect(status.icon).to eq 'icon_status_canceled' + expect(status.label).to eq 'canceled' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is running' do + let(:build) { create(:ci_build, :running) } + + it 'fabricates a canceable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'running' + expect(status.icon).to eq 'icon_status_running' + expect(status.label).to eq 'running' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is pending' do + let(:build) { create(:ci_build, :pending) } + + it 'fabricates a cancelable build status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Cancelable + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'pending' + expect(status.icon).to eq 'icon_status_pending' + expect(status.label).to eq 'pending' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is skipped' do + let(:build) { create(:ci_build, :skipped) } + + it 'fabricates a core skipped status' do + expect(status).to be_a Gitlab::Ci::Status::Skipped + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'skipped' + expect(status.icon).to eq 'icon_status_skipped' + expect(status.label).to eq 'skipped' + expect(status).to have_details + expect(status).not_to have_action + end + end + + context 'when build is a manual action' do + context 'when build is a play action' do + let(:build) { create(:ci_build, :playable) } + + it 'fabricates a core skipped status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Play + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'manual' + expect(status.icon).to eq 'icon_status_manual' + expect(status.label).to eq 'manual play action' + expect(status).to have_details + expect(status).to have_action + end + end + + context 'when build is an environment stop action' do + let(:build) { create(:ci_build, :playable, :teardown_environment) } + + it 'fabricates a core skipped status' do + expect(status).to be_a Gitlab::Ci::Status::Build::Stop + end + + it 'fabricates status with correct details' do + expect(status.text).to eq 'manual' + expect(status.icon).to eq 'icon_status_manual' + expect(status.label).to eq 'manual stop action' + expect(status).to have_details + expect(status).to have_action + end + end + end +end -- cgit v1.2.1 From 7f0ecf3a97a20a0b274ebfd46e762afad05870fc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:18:34 +0100 Subject: Add missing tests for build `cancelable?` method --- spec/models/build_spec.rb | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index e9b4cac5fef..ac596ce4a91 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -899,6 +899,42 @@ describe Ci::Build, models: true do end end + describe '#cancelable?' do + subject { build } + + context 'when build is cancelable' do + context 'when build is pending' do + it { is_expected.to be_cancelable } + end + + context 'when build is running' do + before do + build.run! + end + + it { is_expected.to be_cancelable } + end + end + + context 'when build is not cancelable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.not_to be_cancelable } + end + + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.not_to be_cancelable } + end + end + end + describe '#retryable?' do subject { build } -- cgit v1.2.1 From 8f743edee14374a6d39a3cf5aa0fc884a0d536b8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:26:44 +0100 Subject: Add tests for common build detailed status helpers --- spec/lib/gitlab/ci/status/build/common_spec.rb | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 spec/lib/gitlab/ci/status/build/common_spec.rb diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb new file mode 100644 index 00000000000..40b96b1807b --- /dev/null +++ b/spec/lib/gitlab/ci/status/build/common_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Gitlab::Ci::Status::Build::Common do + let(:user) { create(:user) } + let(:build) { create(:ci_build) } + let(:project) { build.project } + + subject do + Gitlab::Ci::Status::Core + .new(build, user) + .extend(described_class) + end + + describe '#has_action?' do + it { is_expected.not_to have_action } + end + + describe '#has_details?' do + context 'when user has access to read build' do + before { project.team << [user, :developer] } + + it { is_expected.to have_details } + end + + context 'when user does not have access to read build' do + before { project.update(public_builds: false) } + + it { is_expected.not_to have_details } + end + end + + describe '#details_path' do + it 'links to the build details page' do + expect(subject.details_path).to include "builds/#{build.id}" + end + end +end -- cgit v1.2.1 From 24dd70d37834e55a7ead23ae14125e5e12f64d8b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:29:48 +0100 Subject: Extend tests for pipeline detailed status helpers --- spec/lib/gitlab/ci/status/pipeline/common_spec.rb | 30 ++++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb index 2df9d574677..d665674bf70 100644 --- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb @@ -2,29 +2,35 @@ require 'spec_helper' describe Gitlab::Ci::Status::Pipeline::Common do let(:user) { create(:user) } - let(:project) { create(:empty_project) } + let(:project) { create(:empty_project, :private) } let(:pipeline) { create(:ci_pipeline, project: project) } subject do - Class.new(Gitlab::Ci::Status::Core) + Gitlab::Ci::Status::Core .new(pipeline, user) .extend(described_class) end - before do - project.team << [user, :developer] + describe '#has_action?' do + it { is_expected.not_to have_action } end - it 'does not have action' do - expect(subject).not_to have_action - end + describe '#has_details?' do + context 'when user has access to read pipeline' do + before { project.team << [user, :developer] } + + it { is_expected.to have_details } + end - it 'has details' do - expect(subject).to have_details + context 'when user does not have access to read pipeline' do + it { is_expected.not_to have_details } + end end - it 'links to the pipeline details page' do - expect(subject.details_path) - .to include "pipelines/#{pipeline.id}" + describe '#details_path' do + it 'links to the pipeline details page' do + expect(subject.details_path) + .to include "pipelines/#{pipeline.id}" + end end end -- cgit v1.2.1 From 00970606d78486a8b6672659e9ea28521a102e44 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:44:43 +0100 Subject: Extract abilities checking module from ability model --- app/models/ability.rb | 6 ------ lib/gitlab/allowable.rb | 8 ++++++++ lib/gitlab/ci/status/core.rb | 2 +- spec/lib/gitlab/allowable_spec.rb | 27 +++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 lib/gitlab/allowable.rb create mode 100644 spec/lib/gitlab/allowable_spec.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index ce461caf686..fa8f8bc3a5f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,10 +1,4 @@ class Ability - module Allowable - def can?(user, action, subject) - Ability.allowed?(user, action, subject) - end - end - class << self # Given a list of users and a project this method returns the users that can # read the given project. diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb new file mode 100644 index 00000000000..3cc218f9db4 --- /dev/null +++ b/lib/gitlab/allowable.rb @@ -0,0 +1,8 @@ +module Gitlab + module Allowable + def can?(user, action, subject) + Ability.allowed?(user, action, subject) + end + end +end + diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index dd3a824e486..12c0ca1d6d5 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -5,7 +5,7 @@ module Gitlab # class Core include Gitlab::Routing.url_helpers - include Ability::Allowable + include Gitlab::Allowable attr_reader :subject, :user diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb new file mode 100644 index 00000000000..87733d53e92 --- /dev/null +++ b/spec/lib/gitlab/allowable_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Gitlab::Allowable do + subject do + Class.new.include(described_class).new + end + + describe '#can?' do + let(:user) { create(:user) } + + context 'when user is allowed to do something' do + let(:project) { create(:empty_project, :public) } + + it 'reports correct ability to perform action' do + expect(subject.can?(user, :read_project, project)).to be true + end + end + + context 'when user is not allowed to do something' do + let(:project) { create(:empty_project, :private) } + + it 'reports correct ability to perform action' do + expect(subject.can?(user, :read_project, project)).to be false + end + end + end +end -- cgit v1.2.1 From 8c639ac23ce67b763cabf3aed4b020f4b961f9be Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 13 Dec 2016 11:51:09 -0200 Subject: Backport hooks on group policies for the EE-specific implementation --- app/policies/group_member_policy.rb | 6 ++++++ app/policies/group_policy.rb | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb index 62335527654..5a3fe814b77 100644 --- a/app/policies/group_member_policy.rb +++ b/app/policies/group_member_policy.rb @@ -15,5 +15,11 @@ class GroupMemberPolicy < BasePolicy elsif @user == target_user can! :destroy_group_member end + + additional_rules! + end + + def additional_rules! + # This is meant to be overriden in EE end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index b65fb68cd88..6f943feb2a7 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -33,6 +33,8 @@ class GroupPolicy < BasePolicy if globally_viewable && @subject.request_access_enabled && !member can! :request_access end + + additional_rules!(master) end def can_read_group? @@ -43,4 +45,8 @@ class GroupPolicy < BasePolicy GroupProjectsFinder.new(@subject).execute(@user).any? end + + def additional_rules!(master) + # This is meant to be overriden in EE + end end -- cgit v1.2.1 From 84290a452dcdd2271337e2301b846c9498b74c86 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:51:23 +0100 Subject: Make it possible to mix `Gitlab::Routing` in --- lib/gitlab/ci/status/core.rb | 2 +- lib/gitlab/routing.rb | 6 ++++++ spec/lib/gitlab/routing_spec.rb | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 spec/lib/gitlab/routing_spec.rb diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 12c0ca1d6d5..46fef8262c1 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -4,7 +4,7 @@ module Gitlab # Base abstract class fore core status # class Core - include Gitlab::Routing.url_helpers + include Gitlab::Routing include Gitlab::Allowable attr_reader :subject, :user diff --git a/lib/gitlab/routing.rb b/lib/gitlab/routing.rb index 5132177de51..632e2d87500 100644 --- a/lib/gitlab/routing.rb +++ b/lib/gitlab/routing.rb @@ -1,5 +1,11 @@ module Gitlab module Routing + extend ActiveSupport::Concern + + included do + include Gitlab::Routing.url_helpers + end + # Returns the URL helpers Module. # # This method caches the output as Rails' "url_helpers" method creates an diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb new file mode 100644 index 00000000000..01d5acfc15b --- /dev/null +++ b/spec/lib/gitlab/routing_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Gitlab::Routing do + context 'when module is included' do + subject do + Class.new.include(described_class).new + end + + it 'makes it possible to access url helpers' do + expect(subject).to respond_to(:namespace_project_path) + end + end + + context 'when module is not included' do + subject do + Class.new.include(described_class.url_helpers).new + end + + it 'exposes url helpers module through a method' do + expect(subject).to respond_to(:namespace_project_path) + end + end +end -- cgit v1.2.1 From f62e6081fd114641e41ffa1614d16c30a742cbaa Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 13 Dec 2016 14:58:56 +0100 Subject: Update manual build icon SVG --- app/views/shared/icons/_icon_status_manual.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/icons/_icon_status_manual.svg b/app/views/shared/icons/_icon_status_manual.svg index ffb00c6f443..c98839f51a9 100755 --- a/app/views/shared/icons/_icon_status_manual.svg +++ b/app/views/shared/icons/_icon_status_manual.svg @@ -1 +1 @@ - + -- cgit v1.2.1 From d95b709a66a5597dced25a2b9df9a1e24fc6d49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 13 Dec 2016 15:53:00 +0100 Subject: Be smarter when finding a sudoed user in API::Helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/user.rb | 4 ---- changelogs/unreleased/25482-fix-api-sudo.yml | 4 ++-- lib/api/helpers.rb | 24 +++++++++++------------- spec/models/user_spec.rb | 11 ----------- spec/requests/api/helpers_spec.rb | 4 ++-- 5 files changed, 15 insertions(+), 32 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index b9bb4a9e3f7..1bd28203523 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -304,10 +304,6 @@ class User < ActiveRecord::Base personal_access_token.user if personal_access_token end - def by_username_or_id(name_or_id) - find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i) - end - # Returns a user for the given SSH key. def find_by_ssh_key_id(key_id) find_by(id: Key.unscoped.select(:user_id).where(id: key_id)) diff --git a/changelogs/unreleased/25482-fix-api-sudo.yml b/changelogs/unreleased/25482-fix-api-sudo.yml index 3b23bfd3a21..4c11fe1622e 100644 --- a/changelogs/unreleased/25482-fix-api-sudo.yml +++ b/changelogs/unreleased/25482-fix-api-sudo.yml @@ -1,4 +1,4 @@ --- -title: 'API: Memoize the current_user so that the sudo can work properly' +title: 'API: Memoize the current_user so that sudo can work properly' merge_request: 8017 -author: +author: diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2041f0dac6b..8260fcab80a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -34,6 +34,14 @@ module API @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute end + def find_user(id) + if id =~ /^\d+$/ + User.find_by(id: id) + else + User.find_by(username: id) + end + end + def find_project(id) if id =~ /^\d+$/ Project.find_by(id: id) @@ -349,7 +357,7 @@ module API def sudo! return unless sudo_identifier - return unless initial_current_user.is_a?(User) + return unless initial_current_user unless initial_current_user.is_admin? forbidden!('Must be admin to use sudo') @@ -360,7 +368,7 @@ module API forbidden!('Private token must be specified in order to use sudo') end - sudoed_user = User.by_username_or_id(sudo_identifier) + sudoed_user = find_user(sudo_identifier) if sudoed_user @current_user = sudoed_user @@ -370,17 +378,7 @@ module API end def sudo_identifier - return @sudo_identifier if defined?(@sudo_identifier) - - identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] - - # Regex for integers - @sudo_identifier = - if !!(identifier =~ /\A[0-9]+\z/) - identifier.to_i - else - identifier - end + @sudo_identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER] end def add_pagination_headers(paginated_data) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index bad6ed9e146..8b20ee81614 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -727,17 +727,6 @@ describe User, models: true do end end - describe 'by_username_or_id' do - let(:user1) { create(:user, username: 'foo') } - - it "gets the correct user" do - expect(User.by_username_or_id(user1.id)).to eq(user1) - expect(User.by_username_or_id('foo')).to eq(user1) - expect(User.by_username_or_id(-1)).to be_nil - expect(User.by_username_or_id('bar')).to be_nil - end - end - describe '.find_by_ssh_key_id' do context 'using an existing SSH key ID' do let(:user) { create(:user) } diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index 573154f69b4..4035fd97af5 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -16,14 +16,14 @@ describe API::Helpers, api: true do clear_env clear_param env[API::Helpers::PRIVATE_TOKEN_HEADER] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token - env[API::Helpers::SUDO_HEADER] = identifier + env[API::Helpers::SUDO_HEADER] = identifier.to_s end def set_param(user_or_token, identifier) clear_env clear_param params[API::Helpers::PRIVATE_TOKEN_PARAM] = user_or_token.respond_to?(:private_token) ? user_or_token.private_token : user_or_token - params[API::Helpers::SUDO_PARAM] = identifier + params[API::Helpers::SUDO_PARAM] = identifier.to_s end def clear_env -- cgit v1.2.1 From 1a81fcfbd8261862c9614f0ea317af02ae20b24a Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 12 Dec 2016 13:04:13 +0100 Subject: API: Ability to cherry-pick a commit --- changelogs/unreleased/api-cherry-pick.yml | 4 ++ doc/api/commits.md | 39 +++++++++++++++++ lib/api/commits.rb | 36 +++++++++++++++- spec/requests/api/commits_spec.rb | 70 +++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/api-cherry-pick.yml diff --git a/changelogs/unreleased/api-cherry-pick.yml b/changelogs/unreleased/api-cherry-pick.yml new file mode 100644 index 00000000000..5f4cee450b9 --- /dev/null +++ b/changelogs/unreleased/api-cherry-pick.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Ability to cherry pick a commit' +merge_request: 8047 +author: Robert Schilling diff --git a/doc/api/commits.md b/doc/api/commits.md index 0170af00e0e..5c11d0f83bb 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -183,6 +183,44 @@ Example response: } ``` +## Cherry pick a commit + +> [Introduced][ce-8047] in GitLab 8.15. + +Cherry picks a commit to a given branch. + +``` +POST /projects/:id/repository/commits/:sha/cherry_pick +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of a project or NAMESPACE/PROJECT_NAME owned by the authenticated user +| `sha` | string | yes | The commit hash | +| `branch` | string | yes | The name of the branch | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form "branch=master" "https://gitlab.example.com/api/v3/projects/5/repository/commits/master/cherry_pick" +``` + +Example response: + +```json +{ + "id": "8b090c1b79a14f2bd9e8a738f717824ff53aebad", + "short_id": "8b090c1b", + "title": "Feature added", + "author_name": "Dmitriy Zaporozhets", + "author_email": "dmitriy.zaporozhets@gmail.com", + "created_at": "2016-12-12T20:10:39.000+01:00", + "committer_name": "Administrator", + "committer_email": "admin@example.com", + "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets \n" +} +``` + ## Get the diff of a commit Get the diff of a commit in a project. @@ -438,3 +476,4 @@ Example response: ``` [ce-6096]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6096 "Multi-file commit" +[ce-8047]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8047 diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 2670a2d413a..cf2489dbb67 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -1,7 +1,6 @@ require 'mime/types' module API - # Projects commits API class Commits < Grape::API include PaginationParams @@ -121,6 +120,41 @@ module API present paginate(notes), with: Entities::CommitNote end + desc 'Cherry pick commit into a branch' do + detail 'This feature was introduced in GitLab 8.15' + success Entities::RepoCommit + end + params do + requires :sha, type: String, desc: 'A commit sha to be cherry picked' + requires :branch, type: String, desc: 'The name of the branch' + end + post ':id/repository/commits/:sha/cherry_pick' do + authorize! :push_code, user_project + + commit = user_project.commit(params[:sha]) + not_found!('Commit') unless commit + + branch = user_project.repository.find_branch(params[:branch]) + not_found!('Branch') unless branch + + commit_params = { + commit: commit, + create_merge_request: false, + source_project: user_project, + source_branch: commit.cherry_pick_branch_name, + target_branch: params[:branch] + } + + result = ::Commits::CherryPickService.new(user_project, current_user, commit_params).execute + + if result[:status] == :success + branch = user_project.repository.find_branch(params[:branch]) + present user_project.repository.commit(branch.dereferenced_target), with: Entities::RepoCommit + else + render_api_error!(result[:message], 400) + end + end + desc 'Post comment to commit' do success Entities::CommitNote end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index e497bce6943..fd600798eeb 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -456,6 +456,76 @@ describe API::Commits, api: true do end end + describe 'POST :id/repository/commits/:sha/cherry_pick' do + let(:master_pickable_commit) { project.commit('7d3b0f7cff5f37573aea97cebfd5692ea1689924') } + + context 'authorized user' do + it 'cherry picks a commit' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'master' + + expect(response).to have_http_status(201) + expect(json_response['title']).to eq(master_pickable_commit.title) + expect(json_response['message']).to eq(master_pickable_commit.message) + expect(json_response['author_name']).to eq(master_pickable_commit.author_name) + expect(json_response['committer_name']).to eq(user.name) + end + + it 'returns 400 if commit is already included in the target branch' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'markdown' + + expect(response).to have_http_status(400) + expect(json_response['message']).to eq('Sorry, we cannot cherry-pick this commit automatically. + It may have already been cherry-pick, or a more recent commit may have updated some of its content.') + end + + it 'returns 400 if you are not allowed to push to the target branch' do + project.team << [user2, :developer] + protected_branch = create(:protected_branch, project: project, name: 'feature') + + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: 'feature' + + expect(response).to have_http_status(400) + expect(json_response['message']).to eq('You are not allowed to push into this branch') + end + + it 'returns 400 for missing parameters' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('branch is missing') + end + + it 'returns 404 if commit is not found' do + post api("/projects/#{project.id}/repository/commits/abcd0123/cherry_pick", user), branch: 'master' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Commit Not Found') + end + + it 'returns 404 if branch is not found' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user), branch: 'foo' + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Branch Not Found') + end + + it 'returns 400 for missing parameters' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user) + + expect(response).to have_http_status(400) + expect(json_response['error']).to eq('branch is missing') + end + end + + context 'unauthorized user' do + it 'does not cherry pick the commit' do + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick"), branch: 'master' + + expect(response).to have_http_status(401) + end + end + end + describe 'Post comment to commit' do context 'authorized user' do it 'returns comment' do -- cgit v1.2.1 From 51cfd554aff461a4e8649459a53562e990321b3a Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Tue, 13 Dec 2016 08:26:14 +0100 Subject: Make rubocop happy --- spec/requests/api/commits_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index fd600798eeb..5ce229a8cf2 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -482,7 +482,7 @@ describe API::Commits, api: true do project.team << [user2, :developer] protected_branch = create(:protected_branch, project: project, name: 'feature') - post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: 'feature' + post api("/projects/#{project.id}/repository/commits/#{master_pickable_commit.id}/cherry_pick", user2), branch: protected_branch.name expect(response).to have_http_status(400) expect(json_response['message']).to eq('You are not allowed to push into this branch') -- cgit v1.2.1 From 44085354620458ceea3a941b49dc926e38f84a01 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 13 Dec 2016 12:32:27 +0000 Subject: Prevent overflow with vertical scroll when we have space to show content Adds changelog --- app/assets/stylesheets/pages/environments.scss | 8 +++++--- app/assets/stylesheets/pages/pipelines.scss | 17 +++++++---------- changelogs/unreleased/20052-actions-table-vscroll.yml | 4 ++++ 3 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 changelogs/unreleased/20052-actions-table-vscroll.yml diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index de3d2ba549f..e716f24c8e2 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -1,6 +1,8 @@ -.deployments-container { - width: 100%; - overflow: auto; +@media (max-width: $screen-md-max) { + .deployments-container { + width: 100%; + overflow: auto; + } } .environments-list-loading { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 08062b85504..6822f916cc5 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -37,12 +37,13 @@ } } -.content-list { - - &.pipelines, - &.builds-content-list { - width: 100%; - overflow: auto; +@media (max-width: $screen-md-max) { + .content-list { + &.pipelines, + &.builds-content-list { + width: 100%; + overflow: auto; + } } } @@ -666,10 +667,6 @@ min-width: 900px; } - .content-list.pipelines { - overflow: auto; - } - .stage { max-width: 100px; width: 100px; diff --git a/changelogs/unreleased/20052-actions-table-vscroll.yml b/changelogs/unreleased/20052-actions-table-vscroll.yml new file mode 100644 index 00000000000..779cd08de09 --- /dev/null +++ b/changelogs/unreleased/20052-actions-table-vscroll.yml @@ -0,0 +1,4 @@ +--- +title: Prevent overflow with vertical scroll when we have space to show content +merge_request: 8061 +author: -- cgit v1.2.1 From 43af4e5577e5ea338ea5cf072cd0635e3dabcc9e Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 13 Dec 2016 16:49:17 +0100 Subject: Encode when migrating ProcessCommitWorker jobs If the source encoding is not UTF-8 we need to encode the data as `JSON.dump` may throw an error if the input can not be converted to UTF-8. We only encode when necessary to reduce the overhead. Fixes gitlab-org/gitlab-ce#25489 --- .../process-commit-worker-migration-encoding.yml | 4 ++++ ...124141322_migrate_process_commit_worker_jobs.rb | 20 ++++++++++++---- .../migrate_process_commit_worker_jobs_spec.rb | 27 ++++++++++++++++++++-- 3 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/process-commit-worker-migration-encoding.yml diff --git a/changelogs/unreleased/process-commit-worker-migration-encoding.yml b/changelogs/unreleased/process-commit-worker-migration-encoding.yml new file mode 100644 index 00000000000..26aabd9b647 --- /dev/null +++ b/changelogs/unreleased/process-commit-worker-migration-encoding.yml @@ -0,0 +1,4 @@ +--- +title: Encode input when migrating ProcessCommitWorker jobs to prevent migration errors +merge_request: +author: diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb index 453a44e271a..77e0c40d850 100644 --- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb +++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb @@ -47,14 +47,14 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration hash = { id: commit.oid, - message: commit.message, + message: encode(commit.message), parent_ids: commit.parent_ids, authored_date: commit.author[:time], - author_name: commit.author[:name], - author_email: commit.author[:email], + author_name: encode(commit.author[:name]), + author_email: encode(commit.author[:email]), committed_date: commit.committer[:time], - committer_email: commit.committer[:email], - committer_name: commit.committer[:name] + committer_email: encode(commit.committer[:email]), + committer_name: encode(commit.committer[:name]) } payload['args'][2] = hash @@ -89,4 +89,14 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration end end end + + def encode(data) + encoding = Encoding::UTF_8 + + if data.encoding == encoding + data + else + data.encode(encoding, invalid: :replace, undef: :replace) + end + end end diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb index 52428547a9f..6a93deb5412 100644 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb @@ -1,3 +1,5 @@ +# encoding: utf-8 + require 'spec_helper' require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb') @@ -59,6 +61,10 @@ describe MigrateProcessCommitWorkerJobs do Sidekiq.redis { |r| r.llen('queue:process_commit') } end + def pop_job + JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) + end + before do Sidekiq.redis do |redis| job = JSON.dump(args: [project.id, user.id, commit.oid]) @@ -92,11 +98,28 @@ describe MigrateProcessCommitWorkerJobs do expect(job_count).to eq(1) end + it 'encodes data to UTF-8' do + allow_any_instance_of(Rugged::Repository).to receive(:lookup). + with(commit.oid). + and_return(commit) + + allow(commit).to receive(:message). + and_return('김치'.force_encoding('BINARY')) + + migration.up + + job = pop_job + + # We don't care so much about what is being stored, instead we just want + # to make sure the encoding is right so that JSON encoding the data + # doesn't produce any errors. + expect(job['args'][2]['message'].encoding).to eq(Encoding::UTF_8) + end + context 'a migrated job' do let(:job) do migration.up - - JSON.load(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) + pop_job end let(:commit_hash) do -- cgit v1.2.1 From 0469e7dffd13646a211a456341c9459b8ff5f7ad Mon Sep 17 00:00:00 2001 From: jurre Date: Tue, 13 Dec 2016 11:17:32 +0100 Subject: Move admin settings spinach feature to rspec --- features/admin/settings.feature | 19 --------- features/steps/admin/settings.rb | 62 ------------------------------ features/steps/shared/paths.rb | 4 -- spec/features/admin/admin_settings_spec.rb | 53 +++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 85 deletions(-) delete mode 100644 features/admin/settings.feature delete mode 100644 features/steps/admin/settings.rb create mode 100644 spec/features/admin/admin_settings_spec.rb diff --git a/features/admin/settings.feature b/features/admin/settings.feature deleted file mode 100644 index e38eea2cfed..00000000000 --- a/features/admin/settings.feature +++ /dev/null @@ -1,19 +0,0 @@ -@admin -Feature: Admin Settings - Background: - Given I sign in as an admin - And I visit admin settings page - - Scenario: Change application settings - When I modify settings and save form - Then I should see application settings saved - - Scenario: Change Slack Service Template settings - When I click on "Service Templates" - And I click on "Slack" service - And I fill out Slack settings - Then I check all events and submit form - And I should see service template settings saved - Then I click on "Slack" service - And I should see all checkboxes checked - And I should see Slack settings saved diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb deleted file mode 100644 index 11dc7f580f0..00000000000 --- a/features/steps/admin/settings.rb +++ /dev/null @@ -1,62 +0,0 @@ -class Spinach::Features::AdminSettings < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedAdmin - include Gitlab::CurrentSettings - - step 'I modify settings and save form' do - uncheck 'Gravatar enabled' - fill_in 'Home page URL', with: 'https://about.gitlab.com/' - fill_in 'Help page text', with: 'Example text' - click_button 'Save' - end - - step 'I should see application settings saved' do - expect(current_application_settings.gravatar_enabled).to be_falsey - expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/" - expect(page).to have_content "Application settings saved successfully" - end - - step 'I click on "Service Templates"' do - click_link 'Service Templates' - end - - step 'I click on "Slack" service' do - click_link 'Slack' - end - - step 'I check all events and submit form' do - page.check('Active') - page.check('Push') - page.check('Tag push') - page.check('Note') - page.check('Issue') - page.check('Merge request') - page.check('Build') - page.check('Pipeline') - click_on 'Save' - end - - step 'I fill out Slack settings' do - fill_in 'Webhook', with: 'http://localhost' - fill_in 'Username', with: 'test_user' - fill_in 'service_push_channel', with: '#test_channel' - page.check('Notify only broken builds') - end - - step 'I should see service template settings saved' do - expect(page).to have_content 'Application settings saved successfully' - end - - step 'I should see all checkboxes checked' do - page.all('input[type=checkbox]').each do |checkbox| - expect(checkbox).to be_checked - end - end - - step 'I should see Slack settings saved' do - expect(find_field('Webhook').value).to eq 'http://localhost' - expect(find_field('Username').value).to eq 'test_user' - expect(find('#service_push_channel').value).to eq '#test_channel' - end -end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 2bd8ea745e4..d36eff5cf16 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -203,10 +203,6 @@ module SharedPaths visit admin_teams_path end - step 'I visit admin settings page' do - visit admin_application_settings_path - end - step 'I visit spam logs page' do visit admin_spam_logs_path end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb new file mode 100644 index 00000000000..8cd66f189be --- /dev/null +++ b/spec/features/admin/admin_settings_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +feature 'Admin updates settings', feature: true do + before(:each) do + login_as :admin + visit admin_application_settings_path + end + + scenario 'Change application settings' do + uncheck 'Gravatar enabled' + fill_in 'Home page URL', with: 'https://about.gitlab.com/' + fill_in 'Help page text', with: 'Example text' + click_button 'Save' + + expect(current_application_settings.gravatar_enabled).to be_falsey + expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/" + expect(page).to have_content "Application settings saved successfully" + end + + scenario 'Change Slack Service template settings' do + click_link 'Service Templates' + click_link 'Slack' + fill_in 'Webhook', with: 'http://localhost' + fill_in 'Username', with: 'test_user' + fill_in 'service_push_channel', with: '#test_channel' + page.check('Notify only broken builds') + + check_all_events + click_on 'Save' + + expect(page).to have_content 'Application settings saved successfully' + + click_link 'Slack' + + page.all('input[type=checkbox]').each do |checkbox| + expect(checkbox).to be_checked + end + expect(find_field('Webhook').value).to eq 'http://localhost' + expect(find_field('Username').value).to eq 'test_user' + expect(find('#service_push_channel').value).to eq '#test_channel' + end + + def check_all_events + page.check('Active') + page.check('Push') + page.check('Tag push') + page.check('Note') + page.check('Issue') + page.check('Merge request') + page.check('Build') + page.check('Pipeline') + end +end -- cgit v1.2.1 From 309580bd41a178a02894d399e3cd45659a8ba7a7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 14 Dec 2016 08:36:03 +0100 Subject: Remove trailing blank line from Allowable module --- lib/gitlab/allowable.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/gitlab/allowable.rb b/lib/gitlab/allowable.rb index 3cc218f9db4..f48abcc86d5 100644 --- a/lib/gitlab/allowable.rb +++ b/lib/gitlab/allowable.rb @@ -5,4 +5,3 @@ module Gitlab end end end - -- cgit v1.2.1 From de038bf755dcdea842264d9f90572ae92b5f845b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 14 Dec 2016 09:26:19 +0100 Subject: Fix wrong error message expectation in API::Commits spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was because the MR was a bit behind master and the error message got updated in the meantime by 79aad815. Signed-off-by: Rémy Coutable --- spec/requests/api/commits_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 5ce229a8cf2..964cded917c 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -475,7 +475,7 @@ describe API::Commits, api: true do expect(response).to have_http_status(400) expect(json_response['message']).to eq('Sorry, we cannot cherry-pick this commit automatically. - It may have already been cherry-pick, or a more recent commit may have updated some of its content.') + A cherry-pick may have already been performed with this commit, or a more recent commit may have updated some of its content.') end it 'returns 400 if you are not allowed to push to the target branch' do -- cgit v1.2.1 From ac115a9e1f106863112ca9daf463a8f5b8db7d0a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 14 Dec 2016 10:21:16 +0100 Subject: Add some missing tests for detailed status methods --- spec/models/build_spec.rb | 9 +++++++++ spec/models/commit_status_spec.rb | 9 +++++++++ spec/models/generic_commit_status_spec.rb | 16 ++++++++++++++-- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index ac596ce4a91..7f39aff7639 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -1246,4 +1246,13 @@ describe Ci::Build, models: true do it { is_expected.to eq('review/master') } end end + + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns a detailed status' do + expect(build.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Build::Cancelable + end + end end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 1ec08c2a9d0..701f3323c0f 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -234,4 +234,13 @@ describe CommitStatus, models: true do end end end + + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns a detailed status' do + expect(commit_status.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Success + end + end end diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 615cfe3142b..6004bfdb7b7 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,8 +1,11 @@ require 'spec_helper' describe GenericCommitStatus, models: true do - let(:pipeline) { FactoryGirl.create :ci_pipeline } - let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline } + let(:pipeline) { create(:ci_pipeline) } + + let(:generic_commit_status) do + create(:generic_commit_status, pipeline: pipeline) + end describe '#context' do subject { generic_commit_status.context } @@ -17,6 +20,15 @@ describe GenericCommitStatus, models: true do it { is_expected.to eq([:external]) } end + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns detailed status object' do + expect(generic_commit_status.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Success + end + end + describe 'set_default_values' do before do generic_commit_status.context = nil -- cgit v1.2.1 From 6757aaa120c375f15e09f4c428286e5dc1d95f84 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 14 Dec 2016 11:36:19 +0100 Subject: Improve build status specs contexts descriptions --- app/views/ci/status/_badge.html.haml | 2 ++ spec/lib/gitlab/ci/status/build/cancelable_spec.rb | 2 +- spec/lib/gitlab/ci/status/build/play_spec.rb | 2 +- spec/lib/gitlab/ci/status/build/retryable_spec.rb | 2 +- spec/lib/gitlab/ci/status/build/stop_spec.rb | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index c00ddd183fb..f2135af2686 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -1,3 +1,5 @@ +- status = local_assigns.fetch(:status) + - if status.has_details? = link_to status.details_path, class: "ci-status ci-#{status}" do = custom_icon(status.icon) diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb index 989328c0719..9376bce17a1 100644 --- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -65,7 +65,7 @@ describe Gitlab::Ci::Status::Build::Cancelable do describe '.matches?' do subject { described_class.matches?(build, user) } - context 'build is cancelable' do + context 'when build is cancelable' do let(:build) do create(:ci_build, :running) end diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 0fa4405c604..4ddf04a8e11 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -51,7 +51,7 @@ describe Gitlab::Ci::Status::Build::Play do describe '.matches?' do subject { described_class.matches?(build, user) } - context 'build is playable' do + context 'when build is playable' do context 'when build stops an environment' do let(:build) do create(:ci_build, :playable, :teardown_environment) diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb index 59f6b78f99d..d61e5bbaa6b 100644 --- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -65,7 +65,7 @@ describe Gitlab::Ci::Status::Build::Retryable do describe '.matches?' do subject { described_class.matches?(build, user) } - context 'build is retryable' do + context 'when build is retryable' do let(:build) do create(:ci_build, :success) end diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index 984cd1e5007..59a85b55f90 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -53,7 +53,7 @@ describe Gitlab::Ci::Status::Build::Stop do describe '.matches?' do subject { described_class.matches?(build, user) } - context 'build is playable' do + context 'when build is playable' do context 'when build stops an environment' do let(:build) do create(:ci_build, :playable, :teardown_environment) -- cgit v1.2.1