Read about our upcoming Code of Conduct on this issue

entities.rb 40.6 KB
Newer Older
1
module API
Nihad Abbasov's avatar
Nihad Abbasov committed
2
  module Entities
blackst0ne's avatar
blackst0ne committed
3
4
5
6
7
8
9
10
11
12
    class WikiPageBasic < Grape::Entity
      expose :format
      expose :slug
      expose :title
    end

    class WikiPage < WikiPageBasic
      expose :content
    end

13
    class UserSafe < Grape::Entity
14
      expose :id, :name, :username
15
    end
16

17
    class UserBasic < UserSafe
18
      expose :state
19

20
21
22
      expose :avatar_url do |user, options|
        user.avatar_url(only_path: false)
      end
Douwe Maan's avatar
Douwe Maan committed
23

24
      expose :avatar_path, if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path }
25
      expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
26

Douwe Maan's avatar
Douwe Maan committed
27
      expose :web_url do |user, options|
28
        Gitlab::Routing.url_helpers.user_url(user)
Douwe Maan's avatar
Douwe Maan committed
29
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
30
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
31

32
33
    class User < UserBasic
      expose :created_at
34
      expose :bio, :location, :skype, :linkedin, :twitter, :website_url, :organization
35
36
    end

37
38
    class UserActivity < Grape::Entity
      expose :username
39
40
      expose :last_activity_on
      expose :last_activity_on, as: :last_activity_at # Back-compat
41
42
    end

43
44
45
46
    class Identity < Grape::Entity
      expose :provider, :extern_uid
    end

47
    class UserPublic < User
48
49
      expose :last_sign_in_at
      expose :confirmed_at
50
      expose :last_activity_on
51
      expose :email
52
      expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
53
      expose :identities, using: Entities::Identity
54
55
      expose :can_create_group?, as: :can_create_group
      expose :can_create_project?, as: :can_create_project
56
      expose :two_factor_enabled?, as: :two_factor_enabled
57
      expose :external
58
59
    end

60
    class UserWithAdmin < UserPublic
61
      expose :admin?, as: :is_admin
62
63
    end

64
65
66
67
    class Email < Grape::Entity
      expose :id, :email
    end

miks's avatar
miks committed
68
    class Hook < Grape::Entity
69
      expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events
70
      expose :enable_ssl_verification
miks's avatar
miks committed
71
72
    end

73
    class ProjectHook < Hook
74
      expose :project_id, :issues_events, :confidential_issues_events
75
      expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events
76
      expose :job_events
77
78
    end

79
80
81
82
83
84
85
86
    class SharedGroup < Grape::Entity
      expose :group_id
      expose :group_name do |group_link, options|
        group_link.group.name
      end
      expose :group_access, as: :group_access_level
    end

87
88
    class ProjectIdentity < Grape::Entity
      expose :id, :description
89
90
      expose :name, :name_with_namespace
      expose :path, :path_with_namespace
91
92
93
      expose :created_at
    end

94
95
96
97
    class ProjectExportStatus < ProjectIdentity
      include ::API::Helpers::RelatedResourcesHelpers

      expose :export_status
98
      expose :_links, if: lambda { |project, _options| project.export_status == :finished } do
99
100
101
102
103
104
105
106
107
108
        expose :api_url do |project|
          expose_url(api_v4_projects_export_download_path(id: project.id))
        end

        expose :web_url do |project|
          Gitlab::Routing.url_helpers.download_export_project_url(project)
        end
      end
    end

James Lopez's avatar
James Lopez committed
109
110
    class ProjectImportStatus < ProjectIdentity
      expose :import_status
James Lopez's avatar
James Lopez committed
111
112
113

      # TODO: Use `expose_nil` once we upgrade the grape-entity gem
      expose :import_error, if: lambda { |status, _ops| status.import_error }
James Lopez's avatar
James Lopez committed
114
115
    end

116
    class BasicProjectDetails < ProjectIdentity
Francisco Lopez's avatar
Francisco Lopez committed
117
118
119
120
121
122
123
124
125
126
127
      include ::API::ProjectsRelationBuilder

      expose :default_branch
      # Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
      expose :tag_list do |project|
        # project.tags.order(:name).pluck(:name) is the most suitable option
        # to avoid loading all the ActiveRecord objects but, if we use it here
        # it override the preloaded associations and makes a query
        # (fixed in https://github.com/rails/rails/pull/25976).
        project.tags.map(&:name).sort
      end
128
      expose :ssh_url_to_repo, :http_url_to_repo, :web_url
129
130
131
      expose :avatar_url do |project, options|
        project.avatar_url(only_path: false)
      end
132
      expose :star_count, :forks_count
Francisco Lopez's avatar
Francisco Lopez committed
133
      expose :last_activity_at
134

135
136
      expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes

137
      def self.preload_relation(projects_relation, options =  {})
138
        projects_relation.preload(:project_feature, :route)
139
                         .preload(:import_state)
140
141
142
                         .preload(namespace: [:route, :owner],
                                  tags: :taggings)
      end
143
144
    end

145
    class Project < BasicProjectDetails
146
147
148
149
150
151
152
      include ::API::Helpers::RelatedResourcesHelpers

      expose :_links do
        expose :self do |project|
          expose_url(api_v4_projects_path(id: project.id))
        end

153
        expose :issues, if: -> (project, options) { issues_available?(project, options) } do |project|
154
155
156
          expose_url(api_v4_projects_issues_path(id: project.id))
        end

157
        expose :merge_requests, if: -> (project, options) { mrs_available?(project, options) } do |project|
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
          expose_url(api_v4_projects_merge_requests_path(id: project.id))
        end

        expose :repo_branches do |project|
          expose_url(api_v4_projects_repository_branches_path(id: project.id))
        end

        expose :labels do |project|
          expose_url(api_v4_projects_labels_path(id: project.id))
        end

        expose :events do |project|
          expose_url(api_v4_projects_events_path(id: project.id))
        end

        expose :members do |project|
          expose_url(api_v4_projects_members_path(id: project.id))
        end
      end

178
      expose :archived?, as: :archived
179
      expose :visibility
180
      expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
181
      expose :resolve_outdated_diff_discussions
Felipe Artur's avatar
Felipe Artur committed
182
183
184
      expose :container_registry_enabled

      # Expose old field names with the new permissions methods to keep API compatible
185
186
187
      expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
      expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
      expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
188
      expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
189
      expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
Felipe Artur's avatar
Felipe Artur committed
190

191
192
      expose :shared_runners_enabled
      expose :lfs_enabled?, as: :lfs_enabled
193
      expose :creator_id
194
      expose :namespace, using: 'API::Entities::NamespaceBasic'
195
      expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
196
197
      expose :import_status
      expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
198

199
      expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
200
      expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
201
      expose :public_builds, as: :public_jobs
202
      expose :ci_config_path
203
      expose :shared_with_groups do |project, options|
204
        SharedGroup.represent(project.project_group_links, options)
205
      end
206
      expose :only_allow_merge_if_pipeline_succeeds
207
      expose :request_access_enabled
208
      expose :only_allow_merge_if_all_discussions_are_resolved
209
      expose :printing_merge_request_link_enabled
210
      expose :merge_method
Markus Koller's avatar
Markus Koller committed
211
212

      expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
213
214

      def self.preload_relation(projects_relation, options =  {})
215
216
217
218
219
        super(projects_relation).preload(:group)
                                .preload(project_group_links: :group,
                                         fork_network: :root_project,
                                         forked_project_link: :forked_from_project,
                                         forked_from_project: [:route, :forks, namespace: :route, tags: :taggings])
220
221
222
      end

      def self.forks_counting_projects(projects_relation)
223
        projects_relation + projects_relation.map(&:forked_from_project).compact
224
      end
Markus Koller's avatar
Markus Koller committed
225
226
227
228
229
230
231
    end

    class ProjectStatistics < Grape::Entity
      expose :commit_count
      expose :storage_size
      expose :repository_size
      expose :lfs_objects_size
232
      expose :build_artifacts_size, as: :job_artifacts_size
Nihad Abbasov's avatar
Nihad Abbasov committed
233
234
    end

235
236
237
238
    class Member < Grape::Entity
      expose :user, merge: true, using: UserBasic
      expose :access_level
      expose :expires_at
239
240
    end

241
242
243
    class AccessRequester < Grape::Entity
      expose :user, merge: true, using: UserBasic
      expose :requested_at
miks's avatar
miks committed
244
245
    end

246
247
248
249
250
251
252
253
    class BasicGroupDetails < Grape::Entity
      expose :id
      expose :web_url
      expose :name
    end

    class Group < BasicGroupDetails
      expose :path, :description, :visibility
254
      expose :lfs_enabled?, as: :lfs_enabled
255
256
      expose :avatar_url do |group, options|
        group.avatar_url(only_path: false)
257
      end
258
      expose :request_access_enabled
259
      expose :full_name, :full_path
260
261
262
263

      if ::Group.supports_nested_groups?
        expose :parent_id
      end
Markus Koller's avatar
Markus Koller committed
264

265
266
      expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes

Markus Koller's avatar
Markus Koller committed
267
268
269
270
271
      expose :statistics, if: :statistics do
        with_options format_with: -> (value) { value.to_i } do
          expose :storage_size
          expose :repository_size
          expose :lfs_objects_size
272
          expose :build_artifacts_size, as: :job_artifacts_size
Markus Koller's avatar
Markus Koller committed
273
274
        end
      end
275
    end
Andrew8xx8's avatar
Andrew8xx8 committed
276

277
    class GroupDetail < Group
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
      expose :projects, using: Entities::Project do |group, options|
        GroupProjectsFinder.new(
          group: group,
          current_user: options[:current_user],
          options: { only_owned: true }
        ).execute
      end

      expose :shared_projects, using: Entities::Project do |group, options|
        GroupProjectsFinder.new(
          group: group,
          current_user: options[:current_user],
          options: { only_shared: true }
        ).execute
      end
293
294
    end

295
296
297
298
    class DiffRefs < Grape::Entity
      expose :base_sha, :head_sha, :start_sha
    end

299
    class Commit < Grape::Entity
300
301
302
303
304
305
306
      expose :id, :short_id, :title, :created_at
      expose :parent_ids
      expose :safe_message, as: :message
      expose :author_name, :author_email, :authored_date
      expose :committer_name, :committer_email, :committed_date
    end

307
    class CommitStats < Grape::Entity
308
309
310
      expose :additions, :deletions, :total
    end

311
    class CommitDetail < Commit
312
      expose :stats, using: Entities::CommitStats, if: :stats
313
      expose :status
314
      expose :last_pipeline, using: 'API::Entities::PipelineBasic'
315
      expose :project_id
316
317
    end

318
    class BasicRef < Grape::Entity
319
      expose :type, :name
320
321
    end

Robert Schilling's avatar
Robert Schilling committed
322
    class Branch < Grape::Entity
323
324
      expose :name

325
      expose :commit, using: Entities::Commit do |repo_branch, options|
326
        options[:project].repository.commit(repo_branch.dereferenced_target)
327
328
      end

Robert Schilling's avatar
Robert Schilling committed
329
      expose :merged do |repo_branch, options|
330
331
332
333
334
        if options[:merged_branch_names]
          options[:merged_branch_names].include?(repo_branch.name)
        else
          options[:project].repository.merged_to_root_ref?(repo_branch)
        end
Robert Schilling's avatar
Robert Schilling committed
335
336
      end

337
      expose :protected do |repo_branch, options|
Eric's avatar
Eric committed
338
        ::ProtectedBranch.protected?(options[:project], repo_branch.name)
339
340
      end

341
      expose :developers_can_push do |repo_branch, options|
342
        options[:project].protected_branches.developers_can?(:push, repo_branch.name)
343
      end
344

345
      expose :developers_can_merge do |repo_branch, options|
346
        options[:project].protected_branches.developers_can?(:merge, repo_branch.name)
347
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
348
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
349

350
    class TreeObject < Grape::Entity
351
      expose :id, :name, :type, :path
352
353

      expose :mode do |obj, options|
mhasbini's avatar
mhasbini committed
354
        filemode = obj.mode
355
356
357
358
359
        filemode = "0" + filemode if filemode.length < 6
        filemode
      end
    end

Jarka Kadlecová's avatar
Jarka Kadlecová committed
360
    class Snippet < Grape::Entity
361
      expose :id, :title, :file_name, :description
362
      expose :author, using: Entities::UserBasic
Robert Speicher's avatar
Robert Speicher committed
363
      expose :updated_at, :created_at
Jarka Kadlecová's avatar
Jarka Kadlecová committed
364
365
      expose :project_id
      expose :web_url do |snippet|
366
367
        Gitlab::UrlBuilder.build(snippet)
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
368
    end
Nihad Abbasov's avatar
Nihad Abbasov committed
369

Jarka Kadlecová's avatar
Jarka Kadlecová committed
370
371
    class ProjectSnippet < Snippet
    end
372

Jarka Kadlecová's avatar
Jarka Kadlecová committed
373
    class PersonalSnippet < Snippet
374
375
376
377
378
      expose :raw_url do |snippet|
        Gitlab::UrlBuilder.build(snippet) + "/raw"
      end
    end

379
380
    class ProjectEntity < Grape::Entity
      expose :id, :iid
Felipe Artur's avatar
Felipe Artur committed
381
      expose(:project_id) { |entity| entity&.project.try(:id) }
382
383
      expose :title, :description
      expose :state, :created_at, :updated_at
384
385
    end

386
    class Diff < Grape::Entity
micael.bergeron's avatar
micael.bergeron committed
387
      expose :old_path, :new_path, :a_mode, :b_mode
388
389
390
      expose :new_file?, as: :new_file
      expose :renamed_file?, as: :renamed_file
      expose :deleted_file?, as: :deleted_file
391
      expose :json_safe_diff, as: :diff
392
393
    end

Eric's avatar
Eric committed
394
395
396
397
398
399
400
401
402
403
404
405
406
    class ProtectedRefAccess < Grape::Entity
      expose :access_level
      expose :access_level_description do |protected_ref_access|
        protected_ref_access.humanize
      end
    end

    class ProtectedBranch < Grape::Entity
      expose :name
      expose :push_access_levels, using: Entities::ProtectedRefAccess
      expose :merge_access_levels, using: Entities::ProtectedRefAccess
    end

Felipe Artur's avatar
Felipe Artur committed
407
408
    class Milestone < Grape::Entity
      expose :id, :iid
409
410
      expose :project_id, if: -> (entity, options) { entity&.project_id }
      expose :group_id, if: -> (entity, options) { entity&.group_id }
Felipe Artur's avatar
Felipe Artur committed
411
412
      expose :title, :description
      expose :state, :created_at, :updated_at
413
      expose :due_date
414
      expose :start_date
Nihad Abbasov's avatar
Nihad Abbasov committed
415
416
    end

417
    class IssueBasic < ProjectEntity
418
      expose :closed_at
haseeb's avatar
haseeb committed
419
      expose :closed_by, using: Entities::UserBasic
420
421
422
423
      expose :labels do |issue, options|
        # Avoids an N+1 query since labels are preloaded
        issue.labels.map(&:title).sort
      end
424
      expose :milestone, using: Entities::Milestone
425
426
427
428
429
      expose :assignees, :author, using: Entities::UserBasic

      expose :assignee, using: ::API::Entities::UserBasic do |issue, options|
        issue.assignees.first
      end
430

Z.J. van de Weg's avatar
Z.J. van de Weg committed
431
      expose :user_notes_count
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
      expose :upvotes do |issue, options|
        if options[:issuable_metadata]
          # Avoids an N+1 query when metadata is included
          options[:issuable_metadata][issue.id].upvotes
        else
          issue.upvotes
        end
      end
      expose :downvotes do |issue, options|
        if options[:issuable_metadata]
          # Avoids an N+1 query when metadata is included
          options[:issuable_metadata][issue.id].downvotes
        else
          issue.downvotes
        end
      end
448
      expose :due_date
449
      expose :confidential
450
      expose :discussion_locked
451
452
453
454

      expose :web_url do |issue, options|
        Gitlab::UrlBuilder.build(issue)
      end
455
456
457
458

      expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
        issue
      end
Nihad Abbasov's avatar
Nihad Abbasov committed
459
    end
Alex Denisov's avatar
Alex Denisov committed
460

461
    class Issue < IssueBasic
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
      include ::API::Helpers::RelatedResourcesHelpers

      expose :_links do
        expose :self do |issue|
          expose_url(api_v4_project_issue_path(id: issue.project_id, issue_iid: issue.iid))
        end

        expose :notes do |issue|
          expose_url(api_v4_projects_issues_notes_path(id: issue.project_id, noteable_id: issue.iid))
        end

        expose :award_emoji do |issue|
          expose_url(api_v4_projects_issues_award_emoji_path(id: issue.project_id, issue_iid: issue.iid))
        end

        expose :project do |issue|
          expose_url(api_v4_projects_path(id: issue.project_id))
        end
      end

482
483
484
485
486
      expose :subscribed do |issue, options|
        issue.subscribed?(options[:current_user], options[:project] || issue.project)
      end
    end

487
    class IssuableTimeStats < Grape::Entity
488
489
490
491
      format_with(:time_tracking_formatter) do |time_spent|
        Gitlab::TimeTrackingFormatter.output(time_spent)
      end

492
493
494
      expose :time_estimate
      expose :total_time_spent
      expose :human_time_estimate
495
496
497
498
499
500
501
502
503

      with_options(format_with: :time_tracking_formatter) do
        expose :total_time_spent, as: :human_total_time_spent
      end

      def total_time_spent
        # Avoids an N+1 query since timelogs are preloaded
        object.timelogs.map(&:time_spent).sum
      end
504
505
    end

506
507
508
509
510
    class ExternalIssue < Grape::Entity
      expose :title
      expose :id
    end

haseeb's avatar
haseeb committed
511
512
513
514
    class PipelineBasic < Grape::Entity
      expose :id, :sha, :ref, :status
    end

515
516
517
518
519
520
521
    class MergeRequestSimple < ProjectEntity
      expose :title
      expose :web_url do |merge_request, options|
        Gitlab::UrlBuilder.build(merge_request)
      end
    end

522
    class MergeRequestBasic < ProjectEntity
Valery Sizov's avatar
Valery Sizov committed
523
      expose :target_branch, :source_branch
524
525
526
527
528
529
530
531
532
533
534
535
536
537
      expose :upvotes do |merge_request, options|
        if options[:issuable_metadata]
          options[:issuable_metadata][merge_request.id].upvotes
        else
          merge_request.upvotes
        end
      end
      expose :downvotes do |merge_request, options|
        if options[:issuable_metadata]
          options[:issuable_metadata][merge_request.id].downvotes
        else
          merge_request.downvotes
        end
      end
538
539
      expose :author, :assignee, using: Entities::UserBasic
      expose :source_project_id, :target_project_id
540
541
542
543
      expose :labels do |merge_request, options|
        # Avoids an N+1 query since labels are preloaded
        merge_request.labels.map(&:title).sort
      end
544
      expose :work_in_progress?, as: :work_in_progress
545
      expose :milestone, using: Entities::Milestone
546
      expose :merge_when_pipeline_succeeds
547
548
549
550
551
552

      # Ideally we should deprecate `MergeRequest#merge_status` exposure and
      # use `MergeRequest#mergeable?` instead (boolean).
      # See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more
      # information.
      expose :merge_status do |merge_request|
553
554
        merge_request.check_if_can_be_merged
        merge_request.merge_status
555
      end
556
557
      expose :diff_head_sha, as: :sha
      expose :merge_commit_sha
Z.J. van de Weg's avatar
Z.J. van de Weg committed
558
      expose :user_notes_count
559
      expose :discussion_locked
560
561
      expose :should_remove_source_branch?, as: :should_remove_source_branch
      expose :force_remove_source_branch?, as: :force_remove_source_branch
562
      expose :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
563
564
565
566

      expose :web_url do |merge_request, options|
        Gitlab::UrlBuilder.build(merge_request)
      end
567
568
569
570

      expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request|
        merge_request
      end
Alex Denisov's avatar
Alex Denisov committed
571
    end
Valeriy Sizov's avatar
Valeriy Sizov committed
572

573
574
575
576
    class MergeRequest < MergeRequestBasic
      expose :subscribed do |merge_request, options|
        merge_request.subscribed?(options[:current_user], options[:project])
      end
577
578
579
580

      expose :changes_count do |merge_request, _options|
        merge_request.merge_request_diff.real_size
      end
haseeb's avatar
haseeb committed
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613

      expose :merged_by, using: Entities::UserBasic do |merge_request, _options|
        merge_request.metrics&.merged_by
      end

      expose :merged_at do |merge_request, _options|
        merge_request.metrics&.merged_at
      end

      expose :closed_by, using: Entities::UserBasic do |merge_request, _options|
        merge_request.metrics&.latest_closed_by
      end

      expose :closed_at do |merge_request, _options|
        merge_request.metrics&.latest_closed_at
      end

      expose :latest_build_started_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
        merge_request.metrics&.latest_build_started_at
      end

      expose :latest_build_finished_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
        merge_request.metrics&.latest_build_finished_at
      end

      expose :first_deployed_to_production_at, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
        merge_request.metrics&.first_deployed_to_production_at
      end

      expose :pipeline, using: Entities::PipelineBasic, if: -> (_, options) { build_available?(options) } do |merge_request, _options|
        merge_request.metrics&.pipeline
      end

614
615
      expose :diff_refs, using: Entities::DiffRefs

haseeb's avatar
haseeb committed
616
617
618
      def build_available?(options)
        options[:project]&.feature_available?(:builds, options[:current_user])
      end
619
620
    end

621
    class MergeRequestChanges < MergeRequest
622
      expose :diffs, as: :changes, using: Entities::Diff do |compare, _|
Douwe Maan's avatar
Douwe Maan committed
623
        compare.raw_diffs(limits: false).to_a
624
625
626
      end
    end

627
628
629
    class MergeRequestDiff < Grape::Entity
      expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha,
        :created_at, :merge_request_id, :state, :real_size
630
    end
631

632
    class MergeRequestDiffFull < MergeRequestDiff
633
      expose :commits, using: Entities::Commit
634

635
      expose :diffs, using: Entities::Diff do |compare, _|
Douwe Maan's avatar
Douwe Maan committed
636
        compare.raw_diffs(limits: false).to_a
637
638
639
      end
    end

640
    class SSHKey < Grape::Entity
641
      expose :id, :title, :key, :created_at
Valeriy Sizov's avatar
Valeriy Sizov committed
642
    end
643

644
    class SSHKeyWithUser < SSHKey
645
      expose :user, using: Entities::UserPublic
646
647
    end

648
649
650
651
652
    class DeployKeysProject < Grape::Entity
      expose :deploy_key, merge: true, using: Entities::SSHKey
      expose :can_push
    end

Robert Schilling's avatar
Robert Schilling committed
653
654
655
656
    class GPGKey < Grape::Entity
      expose :id, :key, :created_at
    end

657
658
659
660
661
    class DiffPosition < Grape::Entity
      expose :base_sha, :start_sha, :head_sha, :old_path, :new_path,
        :position_type
    end

662
    class Note < Grape::Entity
sue445's avatar
sue445 committed
663
664
665
      # Only Issue and MergeRequest have iid
      NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze

666
      expose :id
Jan Provaznik's avatar
Jan Provaznik committed
667
      expose :type
668
      expose :note, as: :body
669
      expose :attachment_identifier, as: :attachment
670
      expose :author, using: Entities::UserBasic
671
      expose :created_at, :updated_at
672
      expose :system?, as: :system
673
      expose :noteable_id, :noteable_type
sue445's avatar
sue445 committed
674

675
676
677
678
679
680
681
682
      expose :position, if: ->(note, options) { note.diff_note? } do |note|
        note.position.to_h
      end

      expose :resolvable?, as: :resolvable
      expose :resolved?, as: :resolved, if: ->(note, options) { note.resolvable? }
      expose :resolved_by, using: Entities::UserBasic, if: ->(note, options) { note.resolvable? }

sue445's avatar
sue445 committed
683
684
      # Avoid N+1 queries as much as possible
      expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) }
685
    end
686

Jan Provaznik's avatar
Jan Provaznik committed
687
688
689
690
691
692
    class Discussion < Grape::Entity
      expose :id
      expose :individual_note?, as: :individual_note
      expose :notes, using: Entities::Note
    end

693
694
695
696
697
698
699
700
    class AwardEmoji < Grape::Entity
      expose :id
      expose :name
      expose :user, using: Entities::UserBasic
      expose :created_at, :updated_at
      expose :awardable_id, :awardable_type
    end

701
702
703
704
    class MRNote < Grape::Entity
      expose :note
      expose :author, using: Entities::UserBasic
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
705

706
707
    class CommitNote < Grape::Entity
      expose :note
708
709
710
      expose(:path) { |note| note.diff_file.try(:file_path) if note.diff_note? }
      expose(:line) { |note| note.diff_line.try(:new_line) if note.diff_note? }
      expose(:line_type) { |note| note.diff_line.try(:type) if note.diff_note? }
711
      expose :author, using: Entities::UserBasic
712
      expose :created_at
713
714
    end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
715
716
    class CommitStatus < Grape::Entity
      expose :id, :sha, :ref, :status, :name, :target_url, :description,
717
             :created_at, :started_at, :finished_at, :allow_failure, :coverage
Kamil Trzcinski's avatar
Kamil Trzcinski committed
718
      expose :author, using: Entities::UserBasic
Kamil Trzcinski's avatar
Kamil Trzcinski committed
719
720
    end

721
722
723
724
725
    class PushEventPayload < Grape::Entity
      expose :commit_count, :action, :ref_type, :commit_from, :commit_to
      expose :ref, :commit_title
    end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
726
    class Event < Grape::Entity
727
      expose :project_id, :action_name
sue445's avatar
sue445 committed
728
      expose :target_id, :target_iid, :target_type, :author_id
729
      expose :target_title
730
      expose :created_at
731
732
      expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
      expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
733

734
735
736
737
738
      expose :push_event_payload,
        as: :push_data,
        using: PushEventPayload,
        if: -> (event, _) { event.push? }

739
      expose :author_username do |event, options|
Z.J. van de Weg's avatar
Z.J. van de Weg committed
740
        event.author&.username
741
      end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
742
    end
743

744
    class ProjectGroupLink < Grape::Entity
745
      expose :id, :project_id, :group_id, :group_access, :expires_at
746
747
    end

Douglas Barbosa Alexandre's avatar
Douglas Barbosa Alexandre committed
748
749
750
751
    class Todo < Grape::Entity
      expose :id
      expose :project, using: Entities::BasicProjectDetails
      expose :author, using: Entities::UserBasic
Robert Schilling's avatar
Robert Schilling committed
752
      expose :action_name
Douglas Barbosa Alexandre's avatar
Douglas Barbosa Alexandre committed
753
      expose :target_type
754
755

      expose :target do |todo, options|
756
        Entities.const_get(todo.target_type).represent(todo.target, options)
Douglas Barbosa Alexandre's avatar
Douglas Barbosa Alexandre committed
757
758
759
760
761
      end

      expose :target_url do |todo, options|
        target_type   = todo.target_type.underscore
        target_url    = "namespace_project_#{target_type}_url"
762
        target_anchor = "note_#{todo.note_id}" if todo.note_id?
Douglas Barbosa Alexandre's avatar
Douglas Barbosa Alexandre committed
763

764
765
766
        Gitlab::Routing
          .url_helpers
          .public_send(target_url, todo.project.namespace, todo.project, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend
Douglas Barbosa Alexandre's avatar
Douglas Barbosa Alexandre committed
767
768
769
770
771
772
773
      end

      expose :body
      expose :state
      expose :created_at
    end

774
    class NamespaceBasic < Grape::Entity
775
      expose :id, :name, :path, :kind, :full_path, :parent_id
776
    end
777

778
    class Namespace < NamespaceBasic
779
780
781
782
783
784
      expose :members_count_with_descendants, if: -> (namespace, opts) { expose_members_count_with_descendants?(namespace, opts) } do |namespace, _|
        namespace.users_with_descendants.count
      end

      def expose_members_count_with_descendants?(namespace, opts)
        namespace.kind == 'group' && Ability.allowed?(opts[:current_user], :admin_group, namespace)
785
      end
786
    end
787

788
    class MemberAccess < Grape::Entity
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
789
      expose :access_level
790
      expose :notification_level do |member, options|
791
792
793
        if member.notification_setting
          ::NotificationSetting.levels[member.notification_setting.level]
        end
794
      end
795
796
    end

797
    class ProjectAccess < MemberAccess
798
799
    end

800
    class GroupAccess < MemberAccess
801
802
    end

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
    class NotificationSetting < Grape::Entity
      expose :level
      expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do
        ::NotificationSetting::EMAIL_EVENTS.each do |event|
          expose event
        end
      end
    end

    class GlobalNotificationSetting < NotificationSetting
      expose :notification_email do |notification_setting, options|
        notification_setting.user.notification_email
      end
    end

818
819
    class ProjectService < Grape::Entity
      expose :id, :title, :created_at, :updated_at, :active
820
821
      expose :push_events, :issues_events, :confidential_issues_events
      expose :merge_requests_events, :tag_push_events, :note_events
822
      expose :confidential_note_events, :pipeline_events, :wiki_page_events
823
      expose :job_events
824
825
      # Expose serialized properties
      expose :properties do |service, options|
Stan Hu's avatar
Stan Hu committed
826
        service.properties.slice(*service.api_field_names)
827
828
829
      end
    end

830
831
832
    class ProjectWithAccess < Project
      expose :permissions do
        expose :project_access, using: Entities::ProjectAccess do |project, options|
833
          if options.key?(:project_members)
834
835
836
            (options[:project_members] || []).find { |member| member.source_id == project.id }
          else
            project.project_member(options[:current_user])
837
          end
838
839
840
        end

        expose :group_access, using: Entities::GroupAccess do |project, options|
841
          if project.group
842
            if options.key?(:group_members)
843
844
845
              (options[:group_members] || []).find { |member| member.source_id == project.namespace_id }
            else
              project.group.group_member(options[:current_user])
846
            end
847
          end
848
849
        end
      end
850
851
852
853
854
855
856
857
858
859
860
861
862
863

      def self.preload_relation(projects_relation, options = {})
        relation = super(projects_relation, options)

        unless options.key?(:group_members)
          relation = relation.preload(group: [group_members: [:source, user: [notification_settings: :source]]])
        end

        unless options.key?(:project_members)
          relation = relation.preload(project_members: [:source, user: [notification_settings: :source]])
        end

        relation
      end
864
    end
865

Andre Guedes's avatar
Andre Guedes committed
866
    class LabelBasic < Grape::Entity
Rares Sfirlogea's avatar
Rares Sfirlogea committed
867
      expose :id, :name, :color, :description
Andre Guedes's avatar
Andre Guedes committed
868
869
870
    end

    class Label < LabelBasic
871
      expose :open_issues_count do |label, options|
Francesco Coda Zabetta's avatar
Francesco Coda Zabetta committed
872
873
        label.open_issues_count(options[:current_user])
      end
874

Francesco Coda Zabetta's avatar
Francesco Coda Zabetta committed
875
876
877
      expose :closed_issues_count do |label, options|
        label.closed_issues_count(options[:current_user])
      end
878

Francesco Coda Zabetta's avatar
Francesco Coda Zabetta committed
879
880
      expose :open_merge_requests_count do |label, options|
        label.open_merge_requests_count(options[:current_user])
881
882
      end

883
884
885
      expose :priority do |label, options|
        label.priority(options[:project])
      end
886
887

      expose :subscribed do |label, options|
888
        label.subscribed?(options[:current_user], options[:project])
889
      end
890
    end
891

Andre Guedes's avatar
Andre Guedes committed
892
893
894
895
896
897
898
899
    class List < Grape::Entity
      expose :id
      expose :label, using: Entities::LabelBasic
      expose :position
    end

    class Board < Grape::Entity
      expose :id
Felipe Artur's avatar
Felipe Artur committed
900
901
      expose :project, using: Entities::BasicProjectDetails

Andre Guedes's avatar
Andre Guedes committed
902
903
904
905
906
      expose :lists, using: Entities::List do |board|
        board.lists.destroyable
      end
    end

907
    class Compare < Grape::Entity
908
909
      expose :commit, using: Entities::Commit do |compare, options|
        ::Commit.decorate(compare.commits, nil).last
910
      end
911

912
913
      expose :commits, using: Entities::Commit do |compare, options|
        ::Commit.decorate(compare.commits, nil)
914
      end
915

916
      expose :diffs, using: Entities::Diff do |compare, options|
Douwe Maan's avatar
Douwe Maan committed
917
        compare.diffs(limits: false).to_a
918
      end
919
920

      expose :compare_timeout do |compare, options|
921
        compare.diffs.overflow?
922
923
924
      end

      expose :same, as: :compare_same_ref
925
    end
926
927
928
929

    class Contributor < Grape::Entity
      expose :name, :email, :commits, :additions, :deletions
    end
930
931
932
933

    class BroadcastMessage < Grape::Entity
      expose :message, :starts_at, :ends_at, :color, :font
    end
934
935
936

    class ApplicationSetting < Grape::Entity
      expose :id
937
      expose(*::ApplicationSettingsHelper.visible_attributes)
938
939
940
941
942
943
      expose(:restricted_visibility_levels) do |setting, _options|
        setting.restricted_visibility_levels.map { |level| Gitlab::VisibilityLevel.string_level(level) }
      end
      expose(:default_project_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_project_visibility) }
      expose(:default_snippet_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_snippet_visibility) }
      expose(:default_group_visibility) { |setting, _options| Gitlab::VisibilityLevel.string_level(setting.default_group_visibility) }
944
945
946
947

      # support legacy names, can be removed in v5
      expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
      expose :password_authentication_enabled_for_web, as: :signin_enabled
948
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
949
950

    class Release < Grape::Entity
951
952
      expose :tag, as: :tag_name
      expose :description
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
953
    end
954

Robert Schilling's avatar
Robert Schilling committed
955
    class Tag < Grape::Entity
956
      expose :name, :message, :target
957

958
      expose :commit, using: Entities::Commit do |repo_tag, options|
959
        options[:project].repository.commit(repo_tag.dereferenced_target)
960
961
      end

962
963
      expose :release, using: Entities::Release do |repo_tag, options|
        options[:project].releases.find_by(tag: repo_tag.name)
964
965
      end
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
966

967
    class Runner < Grape::Entity
Tomasz Maczukin's avatar
Tomasz Maczukin committed
968
969
970
971
972
      expose :id
      expose :description
      expose :active
      expose :is_shared
      expose :name
973
      expose :online?, as: :online
974
      expose :status
Tomasz Maczukin's avatar
Tomasz Maczukin committed
975
976
    end

977
978
    class RunnerDetails < Runner
      expose :tag_list
979
      expose :run_untagged
980
      expose :locked
981
      expose :maximum_timeout
Shinya Maeda's avatar
Shinya Maeda committed
982
      expose :access_level
983
      expose :version, :revision, :platform, :architecture
984
      expose :contacted_at
blackst0ne's avatar
blackst0ne committed
985
      expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.is_shared? }
986
      expose :projects, with: Entities::BasicProjectDetails do |runner, options|
blackst0ne's avatar
blackst0ne committed
987
        if options[:current_user].admin?