Read about our upcoming Code of Conduct on this issue

issues_controller.rb 8.4 KB
Newer Older
1
2
# frozen_string_literal: true

3
class Projects::IssuesController < Projects::ApplicationController
Douwe Maan's avatar
Douwe Maan committed
4
  include RendersNotes
5
  include ToggleSubscriptionAction
6
  include IssuableActions
7
  include ToggleAwardEmoji
8
  include IssuableCollections
9
  include IssuesCalendar
10
  include SpammableActions
11
  include RecordUserLastActivity
12

13
  def issue_except_actions
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
14
    %i[index calendar new create bulk_update import_csv]
15
16
  end

17
  def set_issuables_index_only_actions
18
19
20
    %i[index calendar]
  end

21
22
  prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
  prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) }
23
  prepend_before_action :authenticate_user!, only: [:new]
24
25
  # designs is only applicable to EE, but defining a prepend_before_action in EE code would overwrite this
  prepend_before_action :store_uri, only: [:new, :show, :designs]
26

27
  before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
28
  before_action :check_issues_available!
29
  before_action :issue, unless: ->(c) { c.issue_except_actions.include?(c.action_name.to_sym) }
30

31
  before_action :set_issuables_index, if: ->(c) { c.set_issuables_index_only_actions.include?(c.action_name.to_sym) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
32
33

  # Allow write(create) issue
34
  before_action :authorize_create_issue!, only: [:new, :create]
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
35
36

  # Allow modify issue
37
  before_action :authorize_update_issuable!, only: [:edit, :update, :move, :reorder]
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
38

39
  # Allow create a new branch and empty WIP merge request from current issue
40
  before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
41

42
  before_action :authorize_import_issues!, only: [:import_csv]
43
  before_action :authorize_download_code!, only: [:related_branches]
44

45
46
47
48
  before_action do
    push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
  end

49
  respond_to :html
gitlabhq's avatar
gitlabhq committed
50

51
52
  alias_method :designs, :show

gitlabhq's avatar
gitlabhq committed
53
  def index
54
    @issues = @issuables
55

gitlabhq's avatar
gitlabhq committed
56
    respond_to do |format|
57
      format.html
58
      format.atom { render layout: 'xml.atom' }
59
60
      format.json do
        render json: {
61
          html: view_to_html_string("projects/issues/_issues"),
Phil Hughes's avatar
Phil Hughes committed
62
          labels: @labels.as_json(methods: :text_color)
63
64
        }
      end
gitlabhq's avatar
gitlabhq committed
65
66
67
    end
  end

68
  def calendar
69
    render_issues_calendar(@issuables)
70
71
  end

gitlabhq's avatar
gitlabhq committed
72
  def new
73
    params[:issue] ||= ActionController::Parameters.new(
74
      assignee_ids: ""
75
    )
76
    build_params = issue_params.merge(
Bob Van Landuyt's avatar
Bob Van Landuyt committed
77
      merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
78
      discussion_to_resolve: params[:discussion_to_resolve]
79
    )
80
    service = Issues::BuildService.new(project, current_user, build_params)
81

82
    @issue = @noteable = service.execute
Bob Van Landuyt's avatar
Bob Van Landuyt committed
83
    @merge_request_to_resolve_discussions_of = service.merge_request_to_resolve_discussions_of
84
    @discussion_to_resolve = service.discussions_to_resolve.first if params[:discussion_to_resolve]
85

gitlabhq's avatar
gitlabhq committed
86
87
88
    respond_with(@issue)
  end

89
90
91
92
  def edit
    respond_with(@issue)
  end

gitlabhq's avatar
gitlabhq committed
93
  def create
94
    create_params = issue_params.merge(spammable_params).merge(
Bob Van Landuyt's avatar
Bob Van Landuyt committed
95
      merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
96
97
98
      discussion_to_resolve: params[:discussion_to_resolve]
    )

99
100
101
    service = Issues::CreateService.new(project, current_user, create_params)
    @issue = service.execute

102
    if service.discussions_to_resolve.count(&:resolved?) > 0
103
      flash[:notice] = if service.discussion_to_resolve_id
104
                         _("Resolved 1 discussion.")
105
                       else
106
                         _("Resolved all discussions.")
107
                       end
108
    end
gitlabhq's avatar
gitlabhq committed
109

110
    respond_to do |format|
111
      format.html do
112
        recaptcha_check_with_fallback { render :new }
113
      end
114
      format.js do
115
116
        @link = @issue.attachment.url.to_js
      end
117
    end
gitlabhq's avatar
gitlabhq committed
118
119
  end

120
121
122
  def move
    params.require(:move_to_project_id)

123
124
    if params[:move_to_project_id].to_i > 0
      new_project = Project.find(params[:move_to_project_id])
125
126
      return render_404 unless issue.can_move?(current_user, new_project)

127
      @issue = Issues::UpdateService.new(project, current_user, target_project: new_project).execute(issue)
128
    end
gitlabhq's avatar
gitlabhq committed
129
130

    respond_to do |format|
131
      format.json do
132
        render_issue_json
133
      end
gitlabhq's avatar
gitlabhq committed
134
    end
135
136

  rescue ActiveRecord::StaleObjectError
137
    render_conflict_response
gitlabhq's avatar
gitlabhq committed
138
139
  end

140
141
142
143
144
145
146
147
148
149
  def reorder
    service = Issues::ReorderService.new(project, current_user, reorder_params)

    if service.execute(issue)
      head :ok
    else
      head :unprocessable_entity
    end
  end

150
  def related_branches
151
    @related_branches = Issues::RelatedBranchesService.new(project, current_user).execute(issue)
152
153
154
155
156
157
158
159
160
161

    respond_to do |format|
      format.json do
        render json: {
          html: view_to_html_string('projects/issues/_related_branches')
        }
      end
    end
  end

162
163
164
  def can_create_branch
    can_create = current_user &&
      can?(current_user, :push_code, @project) &&
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
165
      @issue.can_be_worked_on?
166
167
168

    respond_to do |format|
      format.json do
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
169
        render json: { can_create_branch: can_create, suggested_branch_name: @issue.suggested_branch_name }
170
171
172
173
      end
    end
  end

174
  def create_merge_request
175
    create_params = params.slice(:branch_name, :ref).merge(issue_iid: issue.iid)
176
    create_params[:target_project_id] = params[:target_project_id] if helpers.create_confidential_merge_request_enabled?
177
    result = ::MergeRequests::CreateFromIssueService.new(project, current_user, create_params).execute
178
179
180
181
182
183
184
185

    if result[:status] == :success
      render json: MergeRequestCreateSerializer.new.represent(result[:merge_request])
    else
      render json: result[:messsage], status: :unprocessable_entity
    end
  end

Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
186
  def import_csv
187
188
    if uploader = UploadService.new(project, params[:file]).execute
      ImportIssuesCsvWorker.perform_async(current_user.id, project.id, uploader.upload.id)
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
189
190
191
192
193
194
195

      flash[:notice] = _("Your issues are being imported. Once finished, you'll get a confirmation email.")
    else
      flash[:alert] = _("File upload error.")
    end

    redirect_to project_issues_path(project)
Heinrich Lee Yu's avatar
Heinrich Lee Yu committed
196
197
  end

Nihad Abbasov's avatar
Nihad Abbasov committed
198
  protected
gitlabhq's avatar
gitlabhq committed
199

George Koltsov's avatar
George Koltsov committed
200
  def sorting_field
201
202
203
    Issue::SORTING_PREFERENCE_FIELD
  end

204
  # rubocop: disable CodeReuse/ActiveRecord
gitlabhq's avatar
gitlabhq committed
205
  def issue
206
    return @issue if defined?(@issue)
207

208
    # The Sortable default scope causes performance issues when used with find_by
209
    @issuable = @noteable = @issue ||= @project.issues.includes(author: :status).where(iid: params[:id]).reorder(nil).take!
210
    @note = @project.notes.new(noteable: @issuable)
211
212
213
214

    return render_404 unless can?(current_user, :read_issue, @issue)

    @issue
gitlabhq's avatar
gitlabhq committed
215
  end
216
  # rubocop: enable CodeReuse/ActiveRecord
217
  alias_method :subscribable_resource, :issue
218
  alias_method :issuable, :issue
219
  alias_method :awardable, :issue
220
  alias_method :spammable, :issue
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
221

222
223
224
225
  def spammable_path
    project_issue_path(@project, @issue)
  end

226
  def authorize_create_merge_request!
227
    render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?
228
229
  end

230
231
232
233
234
235
236
237
  def render_issue_json
    if @issue.valid?
      render json: serializer.represent(@issue)
    else
      render json: { errors: @issue.errors.full_messages }, status: :unprocessable_entity
    end
  end

238
  def issue_params
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
    params.require(:issue).permit(*issue_params_attributes)
  end

  def issue_params_attributes
    %i[
      title
      assignee_id
      position
      description
      confidential
      milestone_id
      due_date
      state_event
      task_num
      lock_version
254
      discussion_locked
255
    ] + [{ label_ids: [], assignee_ids: [], update_task: [:index, :checked, :line_number, :line_source] }]
256
  end
257

258
259
260
261
  def reorder_params
    params.permit(:move_before_id, :move_after_id, :group_full_path)
  end

262
  def store_uri
263
264
265
    if request.get? && !request.xhr?
      store_location_for :user, request.fullpath
    end
266
  end
Douwe Maan's avatar
Douwe Maan committed
267
268
269
270

  def serializer
    IssueSerializer.new(current_user: current_user, project: issue.project)
  end
271
272
273
274
275

  def update_service
    update_params = issue_params.merge(spammable_params)
    Issues::UpdateService.new(project, current_user, update_params)
  end
276

277
278
  def finder_type
    IssuesFinder
279
  end
280
281
282
283

  def whitelist_query_limiting
    # Also see the following issues:
    #
284
285
286
287
    # 1. https://gitlab.com/gitlab-org/gitlab-foss/issues/42423
    # 2. https://gitlab.com/gitlab-org/gitlab-foss/issues/42424
    # 3. https://gitlab.com/gitlab-org/gitlab-foss/issues/42426
    Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42422')
288
  end
gitlabhq's avatar
gitlabhq committed
289
end
290
291

Projects::IssuesController.prepend_if_ee('EE::Projects::IssuesController')