Commit 707ce837 authored by Marin Jankovski's avatar Marin Jankovski

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents a8b4c75f efe38da9
......@@ -137,11 +137,13 @@ a {
@include transition(background-color, color, border);
}
.tree-table td,
.well-list > li {
@include transition(background-color, border-color);
}
.stage-nav-item {
@include transition(background-color, box-shadow);
}
.nav-sidebar a,
.dropdown-menu a,
.dropdown-menu button,
.dropdown-menu-nav a {
transition: none;
}
......@@ -118,16 +118,18 @@ module SystemNoteService
#
# Example Note text:
#
# "Changed estimate of this issue to 3d 5h"
# "removed time estimate"
#
# "changed time estimate to 3d 5h"
#
# Returns the created Note object
def change_time_estimate(noteable, project, author)
parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate)
body = if noteable.time_estimate == 0
"Removed time estimate on this #{noteable.human_class_name}"
"removed time estimate"
else
"Changed time estimate of this #{noteable.human_class_name} to #{parsed_time}"
"changed time estimate to #{parsed_time}"
end
create_note(noteable: noteable, project: project, author: author, note: body)
......@@ -142,7 +144,9 @@ module SystemNoteService
#
# Example Note text:
#
# "Added 2h 30m of time spent on this issue"
# "removed time spent"
#
# "added 2h 30m of time spent"
#
# Returns the created Note object
......@@ -150,11 +154,11 @@ module SystemNoteService
time_spent = noteable.time_spent
if time_spent == :reset
body = "Removed time spent on this #{noteable.human_class_name}"
body = "removed time spent"
else
parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs)
action = time_spent > 0 ? 'Added' : 'Subtracted'
body = "#{action} #{parsed_time} of time spent on this #{noteable.human_class_name}"
action = time_spent > 0 ? 'added' : 'subtracted'
body = "#{action} #{parsed_time} of time spent"
end
create_note(noteable: noteable, project: project, author: author, note: body)
......@@ -221,7 +225,7 @@ module SystemNoteService
end
def discussion_continued_in_issue(discussion, project, author, issue)
body = "Added #{issue.to_reference} to continue this discussion"
body = "created #{issue.to_reference} to continue this discussion"
note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body)
note_attributes[:type] = note_attributes.delete(:note_type)
......@@ -260,7 +264,7 @@ module SystemNoteService
#
# Example Note text:
#
# "made the issue confidential"
# "made the issue confidential"
#
# Returns the created Note object
def change_issue_confidentiality(issue, project, author)
......
......@@ -27,7 +27,7 @@
= icon("search", class: "search-icon")
.dropdown
- toggle_text = 'Search for Namespace'
- toggle_text = 'Namespace'
- if params[:namespace_id].present?
- namespace = Namespace.find(params[:namespace_id])
- toggle_text = "#{namespace.kind}: #{namespace.path}"
......@@ -37,8 +37,10 @@
= dropdown_filter("Search for Namespace")
= dropdown_content
= dropdown_loading
= button_tag "Search", class: "btn btn-primary btn-search"
= render 'shared/projects/dropdown'
= link_to new_project_path, class: 'btn btn-new' do
New Project
= button_tag "Search", class: "btn btn-primary btn-search hide"
%ul.nav-links
- opts = params[:visibility_level].present? ? {} : { page: admin_projects_path }
......@@ -56,11 +58,6 @@
= link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do
Public
.nav-controls
= render 'shared/projects/dropdown'
= link_to new_project_path, class: 'btn btn-new' do
New Project
.projects-list-holder
- if @projects.any?
%ul.projects-list.content-list
......
- return unless current_user
.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" do
......
......@@ -2,7 +2,7 @@
- personal = params[:personal]
- archived = params[:archived]
- namespace_id = params[:namespace_id]
.dropdown.inline
.dropdown
- toggle_text = projects_sort_options_hash[@sort]
= dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
......
- return unless current_user
.hidden-xs
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
......@@ -5,29 +7,27 @@
- 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-inverted btn-remove", title: 'Delete Snippet' do
Delete
- if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
- if @snippet.submittable_as_spam? && current_user.admin?
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if current_user
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
%li
= link_to new_snippet_path, title: "New snippet" do
New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
= link_to new_snippet_path, title: "New snippet" do
New snippet
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
- if @snippet.submittable_as_spam? && current_user.admin?
%li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
---
title: Remove hover animation from row elements
merge_request:
author:
---
title: Remove deprecated MR and Issue endpoints and preserve V3 namespace
merge_request: 8967
author:
---
title: Make all system notes lowercase
merge_request: 8807
author:
---
title: Redesign searchbar in admin project list
merge_request: 8776
author:
......@@ -181,7 +181,6 @@ GET /projects/:id/issues?labels=foo,bar
GET /projects/:id/issues?labels=foo,bar&state=opened
GET /projects/:id/issues?milestone=1.0.0
GET /projects/:id/issues?milestone=1.0.0&state=opened
GET /projects/:id/issues?iid=42
```
| Attribute | Type | Required | Description |
......
......@@ -10,8 +10,7 @@ The pagination parameters `page` and `per_page` can be used to restrict the list
GET /projects/:id/merge_requests
GET /projects/:id/merge_requests?state=opened
GET /projects/:id/merge_requests?state=all
GET /projects/:id/merge_requests?iid=42
GET /projects/:id/merge_requests?iid[]=42&iid[]=43
GET /projects/:id/merge_requests?iids[]=42&iids[]=43
```
Parameters:
......
......@@ -7,4 +7,7 @@ changes are in V4:
### Changes
- Removed `/projects/:search` (use: `/projects?search=x`)
- `iid` filter has been removed from `projects/:id/issues`
- `projects/:id/merge_requests?iid[]=x&iid[]=y` array filter has been renamed to `iids`
- Endpoints under `projects/merge_request/:id` have been removed (use: `projects/merge_requests/:id`)
......@@ -5,6 +5,8 @@ module API
version %w(v3 v4), using: :path
version 'v3', using: :path do
mount ::API::V3::Issues
mount ::API::V3::MergeRequests
mount ::API::V3::Projects
end
......
......@@ -15,8 +15,6 @@ module API
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
# TODO: Remove in 9.0 pass `label_name: args.delete(:labels)` to IssuesFinder
......@@ -97,7 +95,6 @@ module API
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :iid, type: Integer, desc: 'Return the issue having the given `iid`'
use :issues_params
end
get ":id/issues" do
......
This diff is collapsed.
module API
module V3
class Issues < Grape::API
include PaginationParams
before { authenticate! }
helpers do
def find_issues(args = {})
args = params.merge(args)
args.delete(:id)
args[:milestone_title] = args.delete(:milestone)
match_all_labels = args.delete(:match_all_labels)
labels = args.delete(:labels)
args[:label_name] = labels if match_all_labels
args[:search] = "#{Issue.reference_prefix}#{args.delete(:iid)}" if args.key?(:iid)
issues = IssuesFinder.new(current_user, args).execute.inc_notes_with_associations
if !match_all_labels && labels.present?
issues = issues.includes(:labels).where('labels.title' => labels.split(','))
end
issues.reorder(args[:order_by] => args[:sort])
end
params :issues_params do
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :milestone, type: String, desc: 'Return issues for a specific milestone'
use :pagination
end
params :issue_params do
optional :description, type: String, desc: 'The description of an issue'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign issue'
optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign issue'
optional :labels, type: String, desc: 'Comma-separated list of label names'
optional :due_date, type: String, desc: 'Date time string in the format YEAR-MONTH-DAY'
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
end
end
resource :issues do
desc "Get currently authenticated user's issues" do
success Entities::Issue
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
use :issues_params
end
get do
issues = find_issues(scope: 'authored')
present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
params do
requires :id, type: String, desc: 'The ID of a group'
end
resource :groups do
desc 'Get a list of group issues' do
success Entities::Issue
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'opened',
desc: 'Return opened, closed, or all issues'
use :issues_params
end
get ":id/issues" do
group = find_group!(params[:id])
issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true)
present paginate(issues), with: Entities::Issue, current_user: current_user
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
detail 'iid filter is deprecated have been removed on V4'
success Entities::Issue
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :iid, type: Integer, desc: 'Return the issue having the given `iid`'
use :issues_params
end
get ":id/issues" do
project = find_project(params[:id])
issues = find_issues(project_id: project.id)
present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
end
desc 'Get a single project issue' do
success Entities::Issue
end
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
end
get ":id/issues/:issue_id" do
issue = find_project_issue(params[:issue_id])
present issue, with: Entities::Issue, current_user: current_user, project: user_project
end
desc 'Create a new project issue' do
success Entities::Issue
end
params do
requires :title, type: String, desc: 'The title of an issue'
optional :created_at, type: DateTime,
desc: 'Date time when the issue was created. Available only for admins and project owners.'
optional :merge_request_for_resolving_discussions, type: Integer,
desc: 'The IID of a merge request for which to resolve discussions'
use :issue_params
end
post ':id/issues' do
# Setting created_at time only allowed for admins and project owners
unless current_user.admin? || user_project.owner == current_user
params.delete(:created_at)
end
issue_params = declared_params(include_missing: false)
if merge_request_iid = params[:merge_request_for_resolving_discussions]
issue_params[:merge_request_for_resolving_discussions] = MergeRequestsFinder.new(current_user, project_id: user_project.id).
execute.
find_by(iid: merge_request_iid)
end
issue = ::Issues::CreateService.new(user_project,
current_user,
issue_params.merge(request: request, api: true)).execute
if issue.spam?
render_api_error!({ error: 'Spam detected' }, 400)
end
if issue.valid?
present issue, with: Entities::Issue, current_user: current_user, project: user_project
else
render_validation_error!(issue)
end
end
desc 'Update an existing issue' do
success Entities::Issue
end
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
optional :title, type: String, desc: 'The title of an issue'
optional :updated_at, type: DateTime,
desc: 'Date time when the issue was updated. Available only for admins and project owners.'
optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
use :issue_params
at_least_one_of :title, :description, :assignee_id, :milestone_id,
:labels, :created_at, :due_date, :confidential, :state_event
end
put ':id/issues/:issue_id' do
issue = user_project.issues.find(params.delete(:issue_id))
authorize! :update_issue, issue
# Setting created_at time only allowed for admins and project owners
unless current_user.admin? || user_project.owner == current_user
params.delete(:updated_at)
end
issue = ::Issues::UpdateService.new(user_project,
current_user,
declared_params(include_missing: false)).execute(issue)
if issue.valid?
present issue, with: Entities::Issue, current_user: current_user, project: user_project
else
render_validation_error!(issue)
end
end
desc 'Move an existing issue' do
success Entities::Issue
end
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end
post ':id/issues/:issue_id/move' do
issue = user_project.issues.find_by(id: params[:issue_id])
not_found!('Issue') unless issue
new_project = Project.find_by(id: params[:to_project_id])
not_found!('Project') unless new_project
begin
issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project)
present issue, with: Entities::Issue, current_user: current_user, project: user_project
rescue ::Issues::MoveService::MoveError => error
render_api_error!(error.message, 400)
end
end
desc 'Delete a project issue'
params do
requires :issue_id, type: Integer, desc: 'The ID of a project issue'
end
delete ":id/issues/:issue_id" do
issue = user_project.issues.find_by(id: params[:issue_id])
not_found!('Issue') unless issue
authorize!(:destroy_issue, issue)
issue.destroy
end
end
end
end
end
This diff is collapsed.
......@@ -612,23 +612,6 @@ describe API::Issues, api: true do
expect(json_response['iid']).to eq(issue.iid)
end
it 'returns a project issue by iid' do
get api("/projects/#{project.id}/issues?iid=#{issue.iid}", user)
expect(response.status).to eq 200
expect(json_response.length).to eq 1
expect(json_response.first['title']).to eq issue.title
expect(json_response.first['id']).to eq issue.id
expect(json_response.first['iid']).to eq issue.iid
end
it 'returns an empty array for an unknown project issue iid' do
get api("/projects/#{project.id}/issues?iid=#{issue.iid + 10}", user)
expect(response.status).to eq 200
expect(json_response.length).to eq 0
end
it "returns 404 if issue id not found" do
get api("/projects/#{project.id}/issues/54321", user)
expect(response).to have_http_status(404)
......
......@@ -73,6 +73,16 @@ describe API::MergeRequests, api: true do
expect(json_response.first['title']).to eq(merge_request_merged.title)
end
it 'returns merge_request by "iids" array' do
get api("/projects/#{project.id}/merge_requests", user), iids: [merge_request.iid, merge_request_closed.iid]
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq merge_request_closed.title
expect(json_response.first['id']).to eq merge_request_closed.id
end
context "with ordering" do
before do
@mr_later = mr_with_later_created_and_updated_at_time
......@@ -159,24 +169,6 @@ describe API::MergeRequests, api: true do
expect(json_response['force_close_merge_request']).to be_falsy
end
it 'returns merge_request by iid' do
url = "/projects/#{project.id}/merge_requests?iid=#{merge_request.iid}"
get api(url, user)
expect(response.status).to eq 200
expect(json_response.first['title']).to eq merge_request.title
expect(json_response.first['id']).to eq merge_request.id
end
it 'returns merge_request by iid array' do
get api("/projects/#{project.id}/merge_requests", user), iid: [merge_request.iid, merge_request_closed.iid]
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.first['title']).to eq merge_request_closed.title
expect(json_response.first['id']).to eq merge_request_closed.id
end
it "returns a 404 error if merge_request_id not found" do
get api("/projects/#{project.id}/merge_requests/999", user)
expect(response).to have_http_status(404)
......
This diff is collapsed.
This diff is collapsed.
......@@ -752,13 +752,13 @@ describe SystemNoteService, services: true do
it 'sets the note text' do
noteable.update_attribute(:time_estimate, 277200)
expect(subject.note).to eq "Changed time estimate of this issue to 1w 4d 5h"
expect(subject.note).to eq "changed time estimate to 1w 4d 5h"
end
end
context 'without a time estimate' do
it 'sets the note text' do
expect(subject.note).to eq "Removed time estimate on this issue"
expect(subject.note).to eq "removed time estimate"
end
end
end
......@@ -782,7 +782,7 @@ describe SystemNoteService, services: true do
it 'sets the note text' do
spend_time!(277200)
expect(subject.note).to eq "Added 1w 4d 5h of time spent on this merge request"
expect(subject.note).to eq "added 1w 4d 5h of time spent"
end
end
......@@ -790,7 +790,7 @@ describe SystemNoteService, services: true do
it 'sets the note text' do
spend_time!(-277200)
expect(subject.note).to eq "Subtracted 1w 4d 5h of time spent on this merge request"
expect(subject.note).to eq "subtracted 1w 4d 5h of time spent"
end
end
......@@ -798,7 +798,7 @@ describe SystemNoteService, services: true do
it 'sets the note text' do
spend_time!(:reset)
expect(subject.note).to eq "Removed time spent on this merge request"
expect(subject.note).to eq "removed time spent"
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment