b70d23c25a
Add spec for task_completion_status Add test cases for task_completion_status result Extracted shared samples Add new spec file for task completion status response Fix style errors Add changelog entry Changed samples to Hashes Remove test for successful request Remove not nil expectation Add task_completion_status to api documentation for issues Add task_completion_status to api documentation for merge_requests Refactor spec so it just requests one specific item Add task_completion_status to Taskable Simplified task completion status in entities Refactor spec so it separates status code check and content check Fix spec description text and field name
1664 lines
52 KiB
Ruby
1664 lines
52 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module API
|
|
module Entities
|
|
class WikiPageBasic < Grape::Entity
|
|
expose :format
|
|
expose :slug
|
|
expose :title
|
|
end
|
|
|
|
class WikiPage < WikiPageBasic
|
|
expose :content
|
|
end
|
|
|
|
class WikiAttachment < Grape::Entity
|
|
include Gitlab::FileMarkdownLinkBuilder
|
|
|
|
expose :file_name
|
|
expose :file_path
|
|
expose :branch
|
|
expose :link do
|
|
expose :file_path, as: :url
|
|
expose :markdown do |_entity|
|
|
self.markdown_link
|
|
end
|
|
end
|
|
|
|
def filename
|
|
object.file_name
|
|
end
|
|
|
|
def secure_url
|
|
object.file_path
|
|
end
|
|
end
|
|
|
|
class UserSafe < Grape::Entity
|
|
expose :id, :name, :username
|
|
end
|
|
|
|
class UserBasic < UserSafe
|
|
expose :state
|
|
|
|
expose :avatar_url do |user, options|
|
|
user.avatar_url(only_path: false)
|
|
end
|
|
|
|
expose :avatar_path, if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path }
|
|
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
|
|
|
|
expose :web_url do |user, options|
|
|
Gitlab::Routing.url_helpers.user_url(user)
|
|
end
|
|
end
|
|
|
|
class User < UserBasic
|
|
expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
|
|
expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization
|
|
end
|
|
|
|
class UserActivity < Grape::Entity
|
|
expose :username
|
|
expose :last_activity_on
|
|
expose :last_activity_on, as: :last_activity_at # Back-compat
|
|
end
|
|
|
|
class Identity < Grape::Entity
|
|
expose :provider, :extern_uid
|
|
end
|
|
|
|
class UserPublic < User
|
|
expose :last_sign_in_at
|
|
expose :confirmed_at
|
|
expose :last_activity_on
|
|
expose :email
|
|
expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
|
|
expose :identities, using: Entities::Identity
|
|
expose :can_create_group?, as: :can_create_group
|
|
expose :can_create_project?, as: :can_create_project
|
|
expose :two_factor_enabled?, as: :two_factor_enabled
|
|
expose :external
|
|
expose :private_profile
|
|
end
|
|
|
|
class UserWithAdmin < UserPublic
|
|
expose :admin?, as: :is_admin
|
|
end
|
|
|
|
class UserDetailsWithAdmin < UserWithAdmin
|
|
expose :highest_role
|
|
end
|
|
|
|
class UserStatus < Grape::Entity
|
|
expose :emoji
|
|
expose :message
|
|
expose :message_html do |entity|
|
|
MarkupHelper.markdown_field(entity, :message)
|
|
end
|
|
end
|
|
|
|
class Email < Grape::Entity
|
|
expose :id, :email
|
|
end
|
|
|
|
class Hook < Grape::Entity
|
|
expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events
|
|
expose :enable_ssl_verification
|
|
end
|
|
|
|
class ProjectHook < Hook
|
|
expose :project_id, :issues_events, :confidential_issues_events
|
|
expose :note_events, :confidential_note_events, :pipeline_events, :wiki_page_events
|
|
expose :job_events
|
|
expose :push_events_branch_filter
|
|
end
|
|
|
|
class SharedGroup < Grape::Entity
|
|
expose :group_id
|
|
expose :group_name do |group_link, options|
|
|
group_link.group.name
|
|
end
|
|
expose :group_full_path do |group_link, options|
|
|
group_link.group.full_path
|
|
end
|
|
expose :group_access, as: :group_access_level
|
|
expose :expires_at
|
|
end
|
|
|
|
class ProjectIdentity < Grape::Entity
|
|
expose :id, :description
|
|
expose :name, :name_with_namespace
|
|
expose :path, :path_with_namespace
|
|
expose :created_at
|
|
end
|
|
|
|
class ProjectExportStatus < ProjectIdentity
|
|
include ::API::Helpers::RelatedResourcesHelpers
|
|
|
|
expose :export_status
|
|
expose :_links, if: lambda { |project, _options| project.export_status == :finished } do
|
|
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
|
|
|
|
class ProjectImportStatus < ProjectIdentity
|
|
expose :import_status
|
|
|
|
# TODO: Use `expose_nil` once we upgrade the grape-entity gem
|
|
expose :import_error, if: lambda { |project, _ops| project.import_state&.last_error } do |project|
|
|
project.import_state.last_error
|
|
end
|
|
end
|
|
|
|
class BasicProjectDetails < ProjectIdentity
|
|
include ::API::ProjectsRelationBuilder
|
|
|
|
expose :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
|
|
# 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
|
|
|
|
expose :ssh_url_to_repo, :http_url_to_repo, :web_url, :readme_url
|
|
|
|
expose :license_url, if: :license do |project|
|
|
license = project.repository.license_blob
|
|
|
|
if license
|
|
Gitlab::Routing.url_helpers.project_blob_url(project, File.join(project.default_branch, license.path))
|
|
end
|
|
end
|
|
|
|
expose :license, with: 'API::Entities::LicenseBasic', if: :license do |project|
|
|
project.repository.license
|
|
end
|
|
|
|
expose :avatar_url do |project, options|
|
|
project.avatar_url(only_path: false)
|
|
end
|
|
|
|
expose :star_count, :forks_count
|
|
expose :last_activity_at
|
|
expose :namespace, using: 'API::Entities::NamespaceBasic'
|
|
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
|
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
def self.preload_relation(projects_relation, options = {})
|
|
# Preloading tags, should be done with using only `:tags`,
|
|
# as `:tags` are defined as: `has_many :tags, through: :taggings`
|
|
# N+1 is solved then by using `subject.tags.map(&:name)`
|
|
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
|
|
projects_relation.preload(:project_feature, :route)
|
|
.preload(:import_state, :tags)
|
|
.preload(namespace: [:route, :owner])
|
|
end
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
end
|
|
|
|
class Project < BasicProjectDetails
|
|
include ::API::Helpers::RelatedResourcesHelpers
|
|
|
|
expose :_links do
|
|
expose :self do |project|
|
|
expose_url(api_v4_projects_path(id: project.id))
|
|
end
|
|
|
|
expose :issues, if: -> (project, options) { issues_available?(project, options) } do |project|
|
|
expose_url(api_v4_projects_issues_path(id: project.id))
|
|
end
|
|
|
|
expose :merge_requests, if: -> (project, options) { mrs_available?(project, options) } do |project|
|
|
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
|
|
|
|
expose :empty_repo?, as: :empty_repo
|
|
expose :archived?, as: :archived
|
|
expose :visibility
|
|
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
|
|
expose :resolve_outdated_diff_discussions
|
|
expose :container_registry_enabled
|
|
|
|
# Expose old field names with the new permissions methods to keep API compatible
|
|
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]) }
|
|
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
|
|
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
|
|
|
|
expose :shared_runners_enabled
|
|
expose :lfs_enabled?, as: :lfs_enabled
|
|
expose :creator_id
|
|
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
|
|
expose :import_status
|
|
|
|
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
|
|
project.import_state&.last_error
|
|
end
|
|
|
|
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
|
|
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
|
|
expose :public_builds, as: :public_jobs
|
|
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
|
|
expose :shared_with_groups do |project, options|
|
|
SharedGroup.represent(project.project_group_links, options)
|
|
end
|
|
expose :only_allow_merge_if_pipeline_succeeds
|
|
expose :request_access_enabled
|
|
expose :only_allow_merge_if_all_discussions_are_resolved
|
|
expose :printing_merge_request_link_enabled
|
|
expose :merge_method
|
|
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
|
|
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
|
|
}
|
|
expose :external_authorization_classification_label
|
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
def self.preload_relation(projects_relation, options = {})
|
|
# Preloading tags, should be done with using only `:tags`,
|
|
# as `:tags` are defined as: `has_many :tags, through: :taggings`
|
|
# N+1 is solved then by using `subject.tags.map(&:name)`
|
|
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
|
|
super(projects_relation).preload(:group)
|
|
.preload(project_group_links: { group: :route },
|
|
fork_network: :root_project,
|
|
fork_network_member: :forked_from_project,
|
|
forked_from_project: [:route, :forks, :tags, namespace: :route])
|
|
end
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
|
|
def self.forks_counting_projects(projects_relation)
|
|
projects_relation + projects_relation.map(&:forked_from_project).compact
|
|
end
|
|
end
|
|
|
|
class ProjectStatistics < Grape::Entity
|
|
expose :commit_count
|
|
expose :storage_size
|
|
expose :repository_size
|
|
expose :wiki_size
|
|
expose :lfs_objects_size
|
|
expose :build_artifacts_size, as: :job_artifacts_size
|
|
end
|
|
|
|
class ProjectDailyFetches < Grape::Entity
|
|
expose :fetch_count, as: :count
|
|
expose :date
|
|
end
|
|
|
|
class ProjectDailyStatistics < Grape::Entity
|
|
expose :fetches do
|
|
expose :total_fetch_count, as: :total
|
|
expose :fetches, as: :days, using: ProjectDailyFetches
|
|
end
|
|
end
|
|
|
|
class Member < Grape::Entity
|
|
expose :user, merge: true, using: UserBasic
|
|
expose :access_level
|
|
expose :expires_at
|
|
end
|
|
|
|
class AccessRequester < Grape::Entity
|
|
expose :user, merge: true, using: UserBasic
|
|
expose :requested_at
|
|
end
|
|
|
|
class BasicGroupDetails < Grape::Entity
|
|
expose :id
|
|
expose :web_url
|
|
expose :name
|
|
end
|
|
|
|
class Group < BasicGroupDetails
|
|
expose :path, :description, :visibility
|
|
expose :lfs_enabled?, as: :lfs_enabled
|
|
expose :avatar_url do |group, options|
|
|
group.avatar_url(only_path: false)
|
|
end
|
|
expose :request_access_enabled
|
|
expose :full_name, :full_path
|
|
|
|
if ::Group.supports_nested_objects?
|
|
expose :parent_id
|
|
end
|
|
|
|
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
|
|
|
|
expose :statistics, if: :statistics do
|
|
with_options format_with: -> (value) { value.to_i } do
|
|
expose :storage_size
|
|
expose :repository_size
|
|
expose :wiki_size
|
|
expose :lfs_objects_size
|
|
expose :build_artifacts_size, as: :job_artifacts_size
|
|
end
|
|
end
|
|
end
|
|
|
|
class GroupDetail < Group
|
|
expose :projects, using: Entities::Project do |group, options|
|
|
projects = GroupProjectsFinder.new(
|
|
group: group,
|
|
current_user: options[:current_user],
|
|
options: { only_owned: true }
|
|
).execute
|
|
|
|
Entities::Project.prepare_relation(projects)
|
|
end
|
|
|
|
expose :shared_projects, using: Entities::Project do |group, options|
|
|
projects = GroupProjectsFinder.new(
|
|
group: group,
|
|
current_user: options[:current_user],
|
|
options: { only_shared: true }
|
|
).execute
|
|
|
|
Entities::Project.prepare_relation(projects)
|
|
end
|
|
end
|
|
|
|
class DiffRefs < Grape::Entity
|
|
expose :base_sha, :head_sha, :start_sha
|
|
end
|
|
|
|
class Commit < Grape::Entity
|
|
expose :id, :short_id, :created_at
|
|
expose :parent_ids
|
|
expose :full_title, as: :title
|
|
expose :safe_message, as: :message
|
|
expose :author_name, :author_email, :authored_date
|
|
expose :committer_name, :committer_email, :committed_date
|
|
end
|
|
|
|
class CommitStats < Grape::Entity
|
|
expose :additions, :deletions, :total
|
|
end
|
|
|
|
class CommitWithStats < Commit
|
|
expose :stats, using: Entities::CommitStats
|
|
end
|
|
|
|
class CommitDetail < Commit
|
|
expose :stats, using: Entities::CommitStats, if: :stats
|
|
expose :status
|
|
expose :last_pipeline, using: 'API::Entities::PipelineBasic'
|
|
expose :project_id
|
|
end
|
|
|
|
class CommitSignature < Grape::Entity
|
|
expose :gpg_key_id
|
|
expose :gpg_key_primary_keyid, :gpg_key_user_name, :gpg_key_user_email
|
|
expose :verification_status
|
|
expose :gpg_key_subkey_id
|
|
end
|
|
|
|
class BasicRef < Grape::Entity
|
|
expose :type, :name
|
|
end
|
|
|
|
class Branch < Grape::Entity
|
|
expose :name
|
|
|
|
expose :commit, using: Entities::Commit do |repo_branch, options|
|
|
options[:project].repository.commit(repo_branch.dereferenced_target)
|
|
end
|
|
|
|
expose :merged do |repo_branch, options|
|
|
if options[:merged_branch_names]
|
|
options[:merged_branch_names].include?(repo_branch.name)
|
|
else
|
|
options[:project].repository.merged_to_root_ref?(repo_branch)
|
|
end
|
|
end
|
|
|
|
expose :protected do |repo_branch, options|
|
|
::ProtectedBranch.protected?(options[:project], repo_branch.name)
|
|
end
|
|
|
|
expose :developers_can_push do |repo_branch, options|
|
|
options[:project].protected_branches.developers_can?(:push, repo_branch.name)
|
|
end
|
|
|
|
expose :developers_can_merge do |repo_branch, options|
|
|
options[:project].protected_branches.developers_can?(:merge, repo_branch.name)
|
|
end
|
|
|
|
expose :can_push do |repo_branch, options|
|
|
Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name)
|
|
end
|
|
|
|
expose :default do |repo_branch, options|
|
|
options[:project].default_branch == repo_branch.name
|
|
end
|
|
end
|
|
|
|
class TreeObject < Grape::Entity
|
|
expose :id, :name, :type, :path
|
|
|
|
expose :mode do |obj, options|
|
|
filemode = obj.mode
|
|
filemode = "0" + filemode if filemode.length < 6
|
|
filemode
|
|
end
|
|
end
|
|
|
|
class Snippet < Grape::Entity
|
|
expose :id, :title, :file_name, :description, :visibility
|
|
expose :author, using: Entities::UserBasic
|
|
expose :updated_at, :created_at
|
|
expose :project_id
|
|
expose :web_url do |snippet|
|
|
Gitlab::UrlBuilder.build(snippet)
|
|
end
|
|
end
|
|
|
|
class ProjectSnippet < Snippet
|
|
end
|
|
|
|
class PersonalSnippet < Snippet
|
|
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.try(:id) }
|
|
expose :title, :description
|
|
expose :state, :created_at, :updated_at
|
|
|
|
# Avoids an N+1 query when metadata is included
|
|
def issuable_metadata(subject, options, method)
|
|
cached_subject = options.dig(:issuable_metadata, subject.id)
|
|
(cached_subject || subject).public_send(method) # rubocop: disable GitlabSecurity/PublicSend
|
|
end
|
|
end
|
|
|
|
class Diff < Grape::Entity
|
|
expose :old_path, :new_path, :a_mode, :b_mode
|
|
expose :new_file?, as: :new_file
|
|
expose :renamed_file?, as: :renamed_file
|
|
expose :deleted_file?, as: :deleted_file
|
|
expose :json_safe_diff, as: :diff
|
|
end
|
|
|
|
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
|
|
|
|
class ProtectedTag < Grape::Entity
|
|
expose :name
|
|
expose :create_access_levels, using: Entities::ProtectedRefAccess
|
|
end
|
|
|
|
class Milestone < Grape::Entity
|
|
expose :id, :iid
|
|
expose :project_id, if: -> (entity, options) { entity&.project_id }
|
|
expose :group_id, if: -> (entity, options) { entity&.group_id }
|
|
expose :title, :description
|
|
expose :state, :created_at, :updated_at
|
|
expose :due_date
|
|
expose :start_date
|
|
|
|
expose :web_url do |milestone, _options|
|
|
Gitlab::UrlBuilder.build(milestone)
|
|
end
|
|
end
|
|
|
|
class IssueBasic < ProjectEntity
|
|
expose :closed_at
|
|
expose :closed_by, using: Entities::UserBasic
|
|
|
|
expose :labels do |issue, options|
|
|
if options[:with_labels_details]
|
|
::API::Entities::LabelBasic.represent(issue.labels.sort_by(&:title))
|
|
else
|
|
issue.labels.map(&:title).sort
|
|
end
|
|
end
|
|
|
|
expose :milestone, using: Entities::Milestone
|
|
expose :assignees, :author, using: Entities::UserBasic
|
|
|
|
expose :assignee, using: ::API::Entities::UserBasic do |issue|
|
|
issue.assignees.first
|
|
end
|
|
|
|
expose(:user_notes_count) { |issue, options| issuable_metadata(issue, options, :user_notes_count) }
|
|
expose(:merge_requests_count) { |issue, options| issuable_metadata(issue, options, :merge_requests_count) }
|
|
expose(:upvotes) { |issue, options| issuable_metadata(issue, options, :upvotes) }
|
|
expose(:downvotes) { |issue, options| issuable_metadata(issue, options, :downvotes) }
|
|
expose :due_date
|
|
expose :confidential
|
|
expose :discussion_locked
|
|
|
|
expose :web_url do |issue|
|
|
Gitlab::UrlBuilder.build(issue)
|
|
end
|
|
|
|
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |issue|
|
|
issue
|
|
end
|
|
|
|
expose :task_completion_status
|
|
end
|
|
|
|
class Issue < IssueBasic
|
|
include ::API::Helpers::RelatedResourcesHelpers
|
|
|
|
expose(:has_tasks) do |issue, _|
|
|
!issue.task_list_items.empty?
|
|
end
|
|
|
|
expose :task_status, if: -> (issue, _) do
|
|
!issue.task_list_items.empty?
|
|
end
|
|
|
|
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
|
|
|
|
expose :subscribed do |issue, options|
|
|
issue.subscribed?(options[:current_user], options[:project] || issue.project)
|
|
end
|
|
end
|
|
|
|
class IssuableTimeStats < Grape::Entity
|
|
format_with(:time_tracking_formatter) do |time_spent|
|
|
Gitlab::TimeTrackingFormatter.output(time_spent)
|
|
end
|
|
|
|
expose :time_estimate
|
|
expose :total_time_spent
|
|
expose :human_time_estimate
|
|
|
|
with_options(format_with: :time_tracking_formatter) do
|
|
expose :total_time_spent, as: :human_total_time_spent
|
|
end
|
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
def total_time_spent
|
|
# Avoids an N+1 query since timelogs are preloaded
|
|
object.timelogs.map(&:time_spent).sum
|
|
end
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
end
|
|
|
|
class ExternalIssue < Grape::Entity
|
|
expose :title
|
|
expose :id
|
|
end
|
|
|
|
class PipelineBasic < Grape::Entity
|
|
expose :id, :sha, :ref, :status
|
|
|
|
expose :web_url do |pipeline, _options|
|
|
Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline)
|
|
end
|
|
end
|
|
|
|
class MergeRequestSimple < ProjectEntity
|
|
expose :title
|
|
expose :web_url do |merge_request, options|
|
|
Gitlab::UrlBuilder.build(merge_request)
|
|
end
|
|
end
|
|
|
|
class MergeRequestBasic < ProjectEntity
|
|
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 :title_html, if: -> (_, options) { options[:render_html] } do |entity|
|
|
MarkupHelper.markdown_field(entity, :title)
|
|
end
|
|
expose :description_html, if: -> (_, options) { options[:render_html] } do |entity|
|
|
MarkupHelper.markdown_field(entity, :description)
|
|
end
|
|
expose :target_branch, :source_branch
|
|
expose(:user_notes_count) { |merge_request, options| issuable_metadata(merge_request, options, :user_notes_count) }
|
|
expose(:upvotes) { |merge_request, options| issuable_metadata(merge_request, options, :upvotes) }
|
|
expose(:downvotes) { |merge_request, options| issuable_metadata(merge_request, options, :downvotes) }
|
|
expose :assignee, using: ::API::Entities::UserBasic do |merge_request|
|
|
merge_request.assignee
|
|
end
|
|
expose :author, :assignees, using: Entities::UserBasic
|
|
|
|
expose :source_project_id, :target_project_id
|
|
expose :labels do |merge_request|
|
|
# Avoids an N+1 query since labels are preloaded
|
|
merge_request.labels.map(&:title).sort
|
|
end
|
|
expose :work_in_progress?, as: :work_in_progress
|
|
expose :milestone, using: Entities::Milestone
|
|
expose :merge_when_pipeline_succeeds
|
|
|
|
# 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|
|
|
merge_request.check_mergeability
|
|
merge_request.merge_status
|
|
end
|
|
expose :diff_head_sha, as: :sha
|
|
expose :merge_commit_sha
|
|
expose :discussion_locked
|
|
expose :should_remove_source_branch?, as: :should_remove_source_branch
|
|
expose :force_remove_source_branch?, as: :force_remove_source_branch
|
|
expose :allow_collaboration, if: -> (merge_request, _) { merge_request.for_fork? }
|
|
# Deprecated
|
|
expose :allow_collaboration, as: :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? }
|
|
|
|
expose :reference do |merge_request, options|
|
|
merge_request.to_reference(options[:project])
|
|
end
|
|
|
|
expose :web_url do |merge_request|
|
|
Gitlab::UrlBuilder.build(merge_request)
|
|
end
|
|
|
|
expose :time_stats, using: 'API::Entities::IssuableTimeStats' do |merge_request|
|
|
merge_request
|
|
end
|
|
|
|
expose :squash
|
|
|
|
expose :task_completion_status
|
|
end
|
|
|
|
class MergeRequest < MergeRequestBasic
|
|
expose :subscribed do |merge_request, options|
|
|
merge_request.subscribed?(options[:current_user], options[:project])
|
|
end
|
|
|
|
expose :changes_count do |merge_request, _options|
|
|
merge_request.merge_request_diff.real_size
|
|
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
|
|
|
|
expose :head_pipeline, using: 'API::Entities::Pipeline'
|
|
|
|
expose :diff_refs, using: Entities::DiffRefs
|
|
|
|
# Allow the status of a rebase to be determined
|
|
expose :merge_error
|
|
expose :rebase_in_progress?, as: :rebase_in_progress, if: -> (_, options) { options[:include_rebase_in_progress] }
|
|
|
|
expose :diverged_commits_count, as: :diverged_commits_count, if: -> (_, options) { options[:include_diverged_commits_count] }
|
|
|
|
def build_available?(options)
|
|
options[:project]&.feature_available?(:builds, options[:current_user])
|
|
end
|
|
|
|
expose :user do
|
|
expose :can_merge do |merge_request, options|
|
|
merge_request.can_be_merged_by?(options[:current_user])
|
|
end
|
|
end
|
|
end
|
|
|
|
class MergeRequestChanges < MergeRequest
|
|
expose :diffs, as: :changes, using: Entities::Diff do |compare, _|
|
|
compare.raw_diffs(limits: false).to_a
|
|
end
|
|
end
|
|
|
|
class MergeRequestDiff < Grape::Entity
|
|
expose :id, :head_commit_sha, :base_commit_sha, :start_commit_sha,
|
|
:created_at, :merge_request_id, :state, :real_size
|
|
end
|
|
|
|
class MergeRequestDiffFull < MergeRequestDiff
|
|
expose :commits, using: Entities::Commit
|
|
|
|
expose :diffs, using: Entities::Diff do |compare, _|
|
|
compare.raw_diffs(limits: false).to_a
|
|
end
|
|
end
|
|
|
|
class SSHKey < Grape::Entity
|
|
expose :id, :title, :key, :created_at
|
|
end
|
|
|
|
class SSHKeyWithUser < SSHKey
|
|
expose :user, using: Entities::UserPublic
|
|
end
|
|
|
|
class DeployKeysProject < Grape::Entity
|
|
expose :deploy_key, merge: true, using: Entities::SSHKey
|
|
expose :can_push
|
|
end
|
|
|
|
class GPGKey < Grape::Entity
|
|
expose :id, :key, :created_at
|
|
end
|
|
|
|
class DiffPosition < Grape::Entity
|
|
expose :base_sha, :start_sha, :head_sha, :old_path, :new_path,
|
|
:position_type
|
|
end
|
|
|
|
class Note < Grape::Entity
|
|
# Only Issue and MergeRequest have iid
|
|
NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze
|
|
|
|
expose :id
|
|
expose :type
|
|
expose :note, as: :body
|
|
expose :attachment_identifier, as: :attachment
|
|
expose :author, using: Entities::UserBasic
|
|
expose :created_at, :updated_at
|
|
expose :system?, as: :system
|
|
expose :noteable_id, :noteable_type
|
|
|
|
expose :position, if: ->(note, options) { note.is_a?(DiffNote) } 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? }
|
|
|
|
# Avoid N+1 queries as much as possible
|
|
expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) }
|
|
end
|
|
|
|
class Discussion < Grape::Entity
|
|
expose :id
|
|
expose :individual_note?, as: :individual_note
|
|
expose :notes, using: Entities::Note
|
|
end
|
|
|
|
class Avatar < Grape::Entity
|
|
expose :avatar_url do |avatarable, options|
|
|
avatarable.avatar_url(only_path: false, size: options[:size])
|
|
end
|
|
end
|
|
|
|
class AwardEmoji < Grape::Entity
|
|
expose :id
|
|
expose :name
|
|
expose :user, using: Entities::UserBasic
|
|
expose :created_at, :updated_at
|
|
expose :awardable_id, :awardable_type
|
|
end
|
|
|
|
class MRNote < Grape::Entity
|
|
expose :note
|
|
expose :author, using: Entities::UserBasic
|
|
end
|
|
|
|
class CommitNote < Grape::Entity
|
|
expose :note
|
|
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? }
|
|
expose :author, using: Entities::UserBasic
|
|
expose :created_at
|
|
end
|
|
|
|
class CommitStatus < Grape::Entity
|
|
expose :id, :sha, :ref, :status, :name, :target_url, :description,
|
|
:created_at, :started_at, :finished_at, :allow_failure, :coverage
|
|
expose :author, using: Entities::UserBasic
|
|
end
|
|
|
|
class PushEventPayload < Grape::Entity
|
|
expose :commit_count, :action, :ref_type, :commit_from, :commit_to
|
|
expose :ref, :commit_title
|
|
end
|
|
|
|
class Event < Grape::Entity
|
|
expose :project_id, :action_name
|
|
expose :target_id, :target_iid, :target_type, :author_id
|
|
expose :target_title
|
|
expose :created_at
|
|
expose :note, using: Entities::Note, if: ->(event, options) { event.note? }
|
|
expose :author, using: Entities::UserBasic, if: ->(event, options) { event.author }
|
|
|
|
expose :push_event_payload,
|
|
as: :push_data,
|
|
using: PushEventPayload,
|
|
if: -> (event, _) { event.push_action? }
|
|
|
|
expose :author_username do |event, options|
|
|
event.author&.username
|
|
end
|
|
end
|
|
|
|
class ProjectGroupLink < Grape::Entity
|
|
expose :id, :project_id, :group_id, :group_access, :expires_at
|
|
end
|
|
|
|
class Todo < Grape::Entity
|
|
expose :id
|
|
expose :project, using: Entities::ProjectIdentity, if: -> (todo, _) { todo.project_id }
|
|
expose :group, using: 'API::Entities::NamespaceBasic', if: -> (todo, _) { todo.group_id }
|
|
expose :author, using: Entities::UserBasic
|
|
expose :action_name
|
|
expose :target_type
|
|
|
|
expose :target do |todo, options|
|
|
todo_options = options.fetch(todo.target_type, {})
|
|
todo_target_class(todo.target_type).represent(todo.target, todo_options)
|
|
end
|
|
|
|
expose :target_url do |todo, options|
|
|
target_type = todo.target_type.underscore
|
|
target_url = "#{todo.parent.class.to_s.underscore}_#{target_type}_url"
|
|
target_anchor = "note_#{todo.note_id}" if todo.note_id?
|
|
|
|
Gitlab::Routing
|
|
.url_helpers
|
|
.public_send(target_url, todo.parent, todo.target, anchor: target_anchor) # rubocop:disable GitlabSecurity/PublicSend
|
|
end
|
|
|
|
expose :body
|
|
expose :state
|
|
expose :created_at
|
|
|
|
def todo_target_class(target_type)
|
|
::API::Entities.const_get(target_type)
|
|
end
|
|
end
|
|
|
|
class NamespaceBasic < Grape::Entity
|
|
expose :id, :name, :path, :kind, :full_path, :parent_id, :avatar_url
|
|
|
|
expose :web_url do |namespace|
|
|
if namespace.user?
|
|
Gitlab::Routing.url_helpers.user_url(namespace.owner)
|
|
else
|
|
namespace.web_url
|
|
end
|
|
end
|
|
end
|
|
|
|
class Namespace < NamespaceBasic
|
|
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)
|
|
end
|
|
end
|
|
|
|
class MemberAccess < Grape::Entity
|
|
expose :access_level
|
|
expose :notification_level do |member, options|
|
|
if member.notification_setting
|
|
::NotificationSetting.levels[member.notification_setting.level]
|
|
end
|
|
end
|
|
end
|
|
|
|
class ProjectAccess < MemberAccess
|
|
end
|
|
|
|
class GroupAccess < MemberAccess
|
|
end
|
|
|
|
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
|
|
|
|
class ProjectService < Grape::Entity
|
|
expose :id, :title, :created_at, :updated_at, :active
|
|
expose :push_events, :issues_events, :confidential_issues_events
|
|
expose :merge_requests_events, :tag_push_events, :note_events
|
|
expose :confidential_note_events, :pipeline_events, :wiki_page_events
|
|
expose :job_events
|
|
# Expose serialized properties
|
|
expose :properties do |service, options|
|
|
service.properties.slice(*service.api_field_names)
|
|
end
|
|
end
|
|
|
|
class ProjectWithAccess < Project
|
|
expose :permissions do
|
|
expose :project_access, using: Entities::ProjectAccess do |project, options|
|
|
if options[:project_members]
|
|
options[:project_members].find { |member| member.source_id == project.id }
|
|
else
|
|
project.project_member(options[:current_user])
|
|
end
|
|
end
|
|
|
|
expose :group_access, using: Entities::GroupAccess do |project, options|
|
|
if project.group
|
|
if options[:group_members]
|
|
options[:group_members].find { |member| member.source_id == project.namespace_id }
|
|
else
|
|
project.group.highest_group_member(options[:current_user])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
def self.preload_relation(projects_relation, options = {})
|
|
relation = super(projects_relation, options)
|
|
|
|
# MySQL doesn't support LIMIT inside an IN subquery
|
|
if Gitlab::Database.mysql?
|
|
project_ids = relation.pluck('projects.id')
|
|
namespace_ids = relation.pluck(:namespace_id)
|
|
else
|
|
project_ids = relation.select('projects.id')
|
|
namespace_ids = relation.select(:namespace_id)
|
|
end
|
|
|
|
options[:project_members] = options[:current_user]
|
|
.project_members
|
|
.where(source_id: project_ids)
|
|
.preload(:source, user: [notification_settings: :source])
|
|
|
|
options[:group_members] = options[:current_user]
|
|
.group_members
|
|
.where(source_id: namespace_ids)
|
|
.preload(:source, user: [notification_settings: :source])
|
|
|
|
relation
|
|
end
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
end
|
|
|
|
class LabelBasic < Grape::Entity
|
|
expose :id, :name, :color, :description, :text_color
|
|
end
|
|
|
|
class Label < LabelBasic
|
|
expose :open_issues_count do |label, options|
|
|
label.open_issues_count(options[:current_user])
|
|
end
|
|
|
|
expose :closed_issues_count do |label, options|
|
|
label.closed_issues_count(options[:current_user])
|
|
end
|
|
|
|
expose :open_merge_requests_count do |label, options|
|
|
label.open_merge_requests_count(options[:current_user])
|
|
end
|
|
|
|
expose :subscribed do |label, options|
|
|
label.subscribed?(options[:current_user], options[:parent])
|
|
end
|
|
end
|
|
|
|
class GroupLabel < Label
|
|
end
|
|
|
|
class ProjectLabel < Label
|
|
expose :priority do |label, options|
|
|
label.priority(options[:parent])
|
|
end
|
|
expose :is_project_label do |label, options|
|
|
label.is_a?(::ProjectLabel)
|
|
end
|
|
end
|
|
|
|
class List < Grape::Entity
|
|
expose :id
|
|
expose :label, using: Entities::LabelBasic
|
|
expose :position
|
|
end
|
|
|
|
class Board < Grape::Entity
|
|
expose :id
|
|
expose :project, using: Entities::BasicProjectDetails
|
|
|
|
expose :lists, using: Entities::List do |board|
|
|
board.lists.destroyable
|
|
end
|
|
end
|
|
|
|
class Compare < Grape::Entity
|
|
expose :commit, using: Entities::Commit do |compare, options|
|
|
::Commit.decorate(compare.commits, nil).last
|
|
end
|
|
|
|
expose :commits, using: Entities::Commit do |compare, options|
|
|
::Commit.decorate(compare.commits, nil)
|
|
end
|
|
|
|
expose :diffs, using: Entities::Diff do |compare, options|
|
|
compare.diffs(limits: false).to_a
|
|
end
|
|
|
|
expose :compare_timeout do |compare, options|
|
|
compare.diffs.overflow?
|
|
end
|
|
|
|
expose :same, as: :compare_same_ref
|
|
end
|
|
|
|
class Contributor < Grape::Entity
|
|
expose :name, :email, :commits, :additions, :deletions
|
|
end
|
|
|
|
class BroadcastMessage < Grape::Entity
|
|
expose :message, :starts_at, :ends_at, :color, :font
|
|
end
|
|
|
|
class ApplicationSetting < Grape::Entity
|
|
def self.exposed_attributes
|
|
attributes = ::ApplicationSettingsHelper.visible_attributes
|
|
attributes.delete(:performance_bar_allowed_group_path)
|
|
attributes.delete(:performance_bar_enabled)
|
|
|
|
attributes
|
|
end
|
|
|
|
expose :id, :performance_bar_allowed_group_id
|
|
expose(*exposed_attributes)
|
|
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) }
|
|
|
|
expose(*::ApplicationSettingsHelper.external_authorization_service_attributes)
|
|
|
|
# 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
|
|
end
|
|
|
|
# deprecated old Release representation
|
|
class TagRelease < Grape::Entity
|
|
expose :tag, as: :tag_name
|
|
expose :description
|
|
end
|
|
|
|
module Releases
|
|
class Link < Grape::Entity
|
|
expose :id
|
|
expose :name
|
|
expose :url
|
|
expose :external?, as: :external
|
|
end
|
|
|
|
class Source < Grape::Entity
|
|
expose :format
|
|
expose :url
|
|
end
|
|
end
|
|
|
|
class Release < Grape::Entity
|
|
expose :name
|
|
expose :tag, as: :tag_name, if: lambda { |_, _| can_download_code? }
|
|
expose :description
|
|
expose :description_html do |entity|
|
|
MarkupHelper.markdown_field(entity, :description)
|
|
end
|
|
expose :created_at
|
|
expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? }
|
|
expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? }
|
|
|
|
expose :assets do
|
|
expose :assets_count, as: :count do |release, _|
|
|
assets_to_exclude = can_download_code? ? [] : [:sources]
|
|
release.assets_count(except: assets_to_exclude)
|
|
end
|
|
expose :sources, using: Entities::Releases::Source, if: lambda { |_, _| can_download_code? }
|
|
expose :links, using: Entities::Releases::Link do |release, options|
|
|
release.links.sorted
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def can_download_code?
|
|
Ability.allowed?(options[:current_user], :download_code, object.project)
|
|
end
|
|
end
|
|
|
|
class Tag < Grape::Entity
|
|
expose :name, :message, :target
|
|
|
|
expose :commit, using: Entities::Commit do |repo_tag, options|
|
|
options[:project].repository.commit(repo_tag.dereferenced_target)
|
|
end
|
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
expose :release, using: Entities::TagRelease do |repo_tag, options|
|
|
options[:project].releases.find_by(tag: repo_tag.name)
|
|
end
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
end
|
|
|
|
class Runner < Grape::Entity
|
|
expose :id
|
|
expose :description
|
|
expose :ip_address
|
|
expose :active
|
|
expose :instance_type?, as: :is_shared
|
|
expose :name
|
|
expose :online?, as: :online
|
|
expose :status
|
|
end
|
|
|
|
class RunnerDetails < Runner
|
|
expose :tag_list
|
|
expose :run_untagged
|
|
expose :locked
|
|
expose :maximum_timeout
|
|
expose :access_level
|
|
expose :version, :revision, :platform, :architecture
|
|
expose :contacted_at
|
|
expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.instance_type? }
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
expose :projects, with: Entities::BasicProjectDetails do |runner, options|
|
|
if options[:current_user].admin?
|
|
runner.projects
|
|
else
|
|
options[:current_user].authorized_projects.where(id: runner.projects)
|
|
end
|
|
end
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
expose :groups, with: Entities::BasicGroupDetails do |runner, options|
|
|
if options[:current_user].admin?
|
|
runner.groups
|
|
else
|
|
options[:current_user].authorized_groups.where(id: runner.groups)
|
|
end
|
|
end
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
end
|
|
|
|
class RunnerRegistrationDetails < Grape::Entity
|
|
expose :id, :token
|
|
end
|
|
|
|
class JobArtifactFile < Grape::Entity
|
|
expose :filename
|
|
expose :cached_size, as: :size
|
|
end
|
|
|
|
class JobArtifact < Grape::Entity
|
|
expose :file_type, :size, :filename, :file_format
|
|
end
|
|
|
|
class JobBasic < Grape::Entity
|
|
expose :id, :status, :stage, :name, :ref, :tag, :coverage, :allow_failure
|
|
expose :created_at, :started_at, :finished_at
|
|
expose :duration
|
|
expose :user, with: User
|
|
expose :commit, with: Commit
|
|
expose :pipeline, with: PipelineBasic
|
|
|
|
expose :web_url do |job, _options|
|
|
Gitlab::Routing.url_helpers.project_job_url(job.project, job)
|
|
end
|
|
end
|
|
|
|
class Job < JobBasic
|
|
# artifacts_file is included in job_artifacts, but kept for backward compatibility (remove in api/v5)
|
|
expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
|
|
expose :job_artifacts, as: :artifacts, using: JobArtifact
|
|
expose :runner, with: Runner
|
|
expose :artifacts_expire_at
|
|
end
|
|
|
|
class JobBasicWithProject < JobBasic
|
|
expose :project, with: ProjectIdentity
|
|
end
|
|
|
|
class Trigger < Grape::Entity
|
|
include ::API::Helpers::Presentable
|
|
|
|
expose :id
|
|
expose :token
|
|
expose :description
|
|
expose :created_at, :updated_at, :last_used
|
|
expose :owner, using: Entities::UserBasic
|
|
end
|
|
|
|
class Variable < Grape::Entity
|
|
expose :variable_type, :key, :value
|
|
expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
|
|
expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) }
|
|
end
|
|
|
|
class Pipeline < PipelineBasic
|
|
expose :before_sha, :tag, :yaml_errors
|
|
|
|
expose :user, with: Entities::UserBasic
|
|
expose :created_at, :updated_at, :started_at, :finished_at, :committed_at
|
|
expose :duration
|
|
expose :coverage
|
|
expose :detailed_status, using: DetailedStatusEntity do |pipeline, options|
|
|
pipeline.detailed_status(options[:current_user])
|
|
end
|
|
end
|
|
|
|
class PipelineSchedule < Grape::Entity
|
|
expose :id
|
|
expose :description, :ref, :cron, :cron_timezone, :next_run_at, :active
|
|
expose :created_at, :updated_at
|
|
expose :owner, using: Entities::UserBasic
|
|
end
|
|
|
|
class PipelineScheduleDetails < PipelineSchedule
|
|
expose :last_pipeline, using: Entities::PipelineBasic
|
|
expose :variables, using: Entities::Variable
|
|
end
|
|
|
|
class EnvironmentBasic < Grape::Entity
|
|
expose :id, :name, :slug, :external_url
|
|
end
|
|
|
|
class Deployment < Grape::Entity
|
|
expose :id, :iid, :ref, :sha, :created_at
|
|
expose :user, using: Entities::UserBasic
|
|
expose :environment, using: Entities::EnvironmentBasic
|
|
expose :deployable, using: Entities::Job
|
|
end
|
|
|
|
class Environment < EnvironmentBasic
|
|
expose :project, using: Entities::BasicProjectDetails
|
|
expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true }
|
|
end
|
|
|
|
class LicenseBasic < Grape::Entity
|
|
expose :key, :name, :nickname
|
|
expose :url, as: :html_url
|
|
expose(:source_url) { |license| license.meta['source'] }
|
|
end
|
|
|
|
class License < LicenseBasic
|
|
expose :popular?, as: :popular
|
|
expose(:description) { |license| license.meta['description'] }
|
|
expose(:conditions) { |license| license.meta['conditions'] }
|
|
expose(:permissions) { |license| license.meta['permissions'] }
|
|
expose(:limitations) { |license| license.meta['limitations'] }
|
|
expose :content
|
|
end
|
|
|
|
class TemplatesList < Grape::Entity
|
|
expose :key
|
|
expose :name
|
|
end
|
|
|
|
class Template < Grape::Entity
|
|
expose :name, :content
|
|
end
|
|
|
|
class BroadcastMessage < Grape::Entity
|
|
expose :id, :message, :starts_at, :ends_at, :color, :font
|
|
expose :active?, as: :active
|
|
end
|
|
|
|
class PersonalAccessToken < Grape::Entity
|
|
expose :id, :name, :revoked, :created_at, :scopes
|
|
expose :active?, as: :active
|
|
expose :expires_at do |personal_access_token|
|
|
personal_access_token.expires_at ? personal_access_token.expires_at.strftime("%Y-%m-%d") : nil
|
|
end
|
|
end
|
|
|
|
class PersonalAccessTokenWithToken < PersonalAccessToken
|
|
expose :token
|
|
end
|
|
|
|
class ImpersonationToken < PersonalAccessToken
|
|
expose :impersonation
|
|
end
|
|
|
|
class ImpersonationTokenWithToken < PersonalAccessTokenWithToken
|
|
expose :impersonation
|
|
end
|
|
|
|
class FeatureGate < Grape::Entity
|
|
expose :key
|
|
expose :value
|
|
end
|
|
|
|
class Feature < Grape::Entity
|
|
expose :name
|
|
expose :state
|
|
expose :gates, using: FeatureGate do |model|
|
|
model.gates.map do |gate|
|
|
value = model.gate_values[gate.key]
|
|
|
|
# By default all gate values are populated. Only show relevant ones.
|
|
if (value.is_a?(Integer) && value.zero?) || (value.is_a?(Set) && value.empty?)
|
|
next
|
|
end
|
|
|
|
{ key: gate.key, value: value }
|
|
end.compact
|
|
end
|
|
end
|
|
|
|
module JobRequest
|
|
class JobInfo < Grape::Entity
|
|
expose :name, :stage
|
|
expose :project_id, :project_name
|
|
end
|
|
|
|
class GitInfo < Grape::Entity
|
|
expose :repo_url, :ref, :sha, :before_sha
|
|
expose :ref_type
|
|
expose :refspecs
|
|
expose :git_depth, as: :depth
|
|
end
|
|
|
|
class RunnerInfo < Grape::Entity
|
|
expose :metadata_timeout, as: :timeout
|
|
expose :runner_session_url
|
|
end
|
|
|
|
class Step < Grape::Entity
|
|
expose :name, :script, :timeout, :when, :allow_failure
|
|
end
|
|
|
|
class Port < Grape::Entity
|
|
expose :number, :protocol, :name
|
|
end
|
|
|
|
class Image < Grape::Entity
|
|
expose :name, :entrypoint
|
|
expose :ports, using: JobRequest::Port
|
|
end
|
|
|
|
class Service < Image
|
|
expose :alias, :command
|
|
end
|
|
|
|
class Artifacts < Grape::Entity
|
|
expose :name
|
|
expose :untracked
|
|
expose :paths
|
|
expose :when
|
|
expose :expire_in
|
|
expose :artifact_type
|
|
expose :artifact_format
|
|
end
|
|
|
|
class Cache < Grape::Entity
|
|
expose :key, :untracked, :paths, :policy
|
|
end
|
|
|
|
class Credentials < Grape::Entity
|
|
expose :type, :url, :username, :password
|
|
end
|
|
|
|
class Dependency < Grape::Entity
|
|
expose :id, :name, :token
|
|
expose :artifacts_file, using: JobArtifactFile, if: ->(job, _) { job.artifacts? }
|
|
end
|
|
|
|
class Response < Grape::Entity
|
|
expose :id
|
|
expose :token
|
|
expose :allow_git_fetch
|
|
|
|
expose :job_info, using: JobInfo do |model|
|
|
model
|
|
end
|
|
|
|
expose :git_info, using: GitInfo do |model|
|
|
model
|
|
end
|
|
|
|
expose :runner_info, using: RunnerInfo do |model|
|
|
model
|
|
end
|
|
|
|
expose :variables
|
|
expose :steps, using: Step
|
|
expose :image, using: Image
|
|
expose :services, using: Service
|
|
expose :artifacts, using: Artifacts
|
|
expose :cache, using: Cache
|
|
expose :credentials, using: Credentials
|
|
expose :dependencies, using: Dependency
|
|
expose :features
|
|
end
|
|
end
|
|
|
|
class UserAgentDetail < Grape::Entity
|
|
expose :user_agent
|
|
expose :ip_address
|
|
expose :submitted, as: :akismet_submitted
|
|
end
|
|
|
|
class CustomAttribute < Grape::Entity
|
|
expose :key
|
|
expose :value
|
|
end
|
|
|
|
class PagesDomainCertificateExpiration < Grape::Entity
|
|
expose :expired?, as: :expired
|
|
expose :expiration
|
|
end
|
|
|
|
class PagesDomainCertificate < Grape::Entity
|
|
expose :subject
|
|
expose :expired?, as: :expired
|
|
expose :certificate
|
|
expose :certificate_text
|
|
end
|
|
|
|
class PagesDomainBasic < Grape::Entity
|
|
expose :domain
|
|
expose :url
|
|
expose :project_id
|
|
expose :verified?, as: :verified
|
|
expose :verification_code, as: :verification_code
|
|
expose :enabled_until
|
|
|
|
expose :certificate,
|
|
as: :certificate_expiration,
|
|
if: ->(pages_domain, _) { pages_domain.certificate? },
|
|
using: PagesDomainCertificateExpiration do |pages_domain|
|
|
pages_domain
|
|
end
|
|
end
|
|
|
|
class PagesDomain < Grape::Entity
|
|
expose :domain
|
|
expose :url
|
|
expose :verified?, as: :verified
|
|
expose :verification_code, as: :verification_code
|
|
expose :enabled_until
|
|
|
|
expose :certificate,
|
|
if: ->(pages_domain, _) { pages_domain.certificate? },
|
|
using: PagesDomainCertificate do |pages_domain|
|
|
pages_domain
|
|
end
|
|
end
|
|
|
|
class Application < Grape::Entity
|
|
expose :id
|
|
expose :uid, as: :application_id
|
|
expose :name, as: :application_name
|
|
expose :redirect_uri, as: :callback_url
|
|
end
|
|
|
|
# Use with care, this exposes the secret
|
|
class ApplicationWithSecret < Application
|
|
expose :secret
|
|
end
|
|
|
|
class Blob < Grape::Entity
|
|
expose :basename
|
|
expose :data
|
|
expose :filename
|
|
expose :id
|
|
expose :ref
|
|
expose :startline
|
|
expose :project_id
|
|
end
|
|
|
|
class BasicBadgeDetails < Grape::Entity
|
|
expose :link_url
|
|
expose :image_url
|
|
expose :rendered_link_url do |badge, options|
|
|
badge.rendered_link_url(options.fetch(:project, nil))
|
|
end
|
|
expose :rendered_image_url do |badge, options|
|
|
badge.rendered_image_url(options.fetch(:project, nil))
|
|
end
|
|
end
|
|
|
|
class Badge < BasicBadgeDetails
|
|
expose :id
|
|
expose :kind do |badge|
|
|
badge.type == 'ProjectBadge' ? 'project' : 'group'
|
|
end
|
|
end
|
|
|
|
class ResourceLabelEvent < Grape::Entity
|
|
expose :id
|
|
expose :user, using: Entities::UserBasic
|
|
expose :created_at
|
|
expose :resource_type do |event, options|
|
|
event.issuable.class.name
|
|
end
|
|
expose :resource_id do |event, options|
|
|
event.issuable.id
|
|
end
|
|
expose :label, using: Entities::LabelBasic
|
|
expose :action
|
|
end
|
|
|
|
class Suggestion < Grape::Entity
|
|
expose :id
|
|
expose :from_line
|
|
expose :to_line
|
|
expose :appliable?, as: :appliable
|
|
expose :applied
|
|
expose :from_content
|
|
expose :to_content
|
|
end
|
|
|
|
module Platform
|
|
class Kubernetes < Grape::Entity
|
|
expose :api_url
|
|
expose :namespace
|
|
expose :authorization_type
|
|
expose :ca_cert
|
|
end
|
|
end
|
|
|
|
module Provider
|
|
class Gcp < Grape::Entity
|
|
expose :cluster_id
|
|
expose :status_name
|
|
expose :gcp_project_id
|
|
expose :zone
|
|
expose :machine_type
|
|
expose :num_nodes
|
|
expose :endpoint
|
|
end
|
|
end
|
|
|
|
class Cluster < Grape::Entity
|
|
expose :id, :name, :created_at, :domain
|
|
expose :provider_type, :platform_type, :environment_scope, :cluster_type
|
|
expose :user, using: Entities::UserBasic
|
|
expose :platform_kubernetes, using: Entities::Platform::Kubernetes
|
|
expose :provider_gcp, using: Entities::Provider::Gcp
|
|
end
|
|
|
|
class ClusterProject < Cluster
|
|
expose :project, using: Entities::BasicProjectDetails
|
|
end
|
|
end
|
|
end
|