diff options
author | Eric Eastwood <contact@ericeastwood.com> | 2018-03-27 12:39:18 -0500 |
---|---|---|
committer | Eric Eastwood <contact@ericeastwood.com> | 2018-03-27 12:39:18 -0500 |
commit | de0c4730bb83b94195678e5ad4865ea648e6585f (patch) | |
tree | 3ea0fd48c5736b0f5b328f117c3db0508a371d22 /app/serializers | |
parent | dc5b5130d573a48fc34fa62862a1ad6da776b0d0 (diff) | |
parent | a1cde68d208437a470267e28ccff66fe8be88c2e (diff) | |
download | gitlab-ce-de0c4730bb83b94195678e5ad4865ea648e6585f.tar.gz |
Merge branch 'master' into add-canary-favicon
Diffstat (limited to 'app/serializers')
64 files changed, 801 insertions, 183 deletions
diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index ad7ad020b03..bdc22d71202 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -35,6 +35,6 @@ class AnalyticsBuildEntity < Grape::Entity private def url_to(route, build, id = nil) - public_send("#{route}_url", build.project.namespace, build.project, id || build) + public_send("#{route}_url", build.project.namespace, build.project, id || build) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb index 44c50f18613..b7d95ea020f 100644 --- a/app/serializers/analytics_issue_entity.rb +++ b/app/serializers/analytics_issue_entity.rb @@ -24,6 +24,6 @@ class AnalyticsIssueEntity < Grape::Entity private def url_to(route, id) - public_send("#{route}_url", request.project.namespace, request.project, id) + public_send("#{route}_url", request.project.namespace, request.project, id) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb index 564612202b5..3e355a13e06 100644 --- a/app/serializers/analytics_stage_entity.rb +++ b/app/serializers/analytics_stage_entity.rb @@ -7,6 +7,7 @@ class AnalyticsStageEntity < Grape::Entity expose :description expose :median, as: :value do |stage| - stage.median && !stage.median.zero? ? distance_of_time_in_words(stage.median) : nil + # median returns a BatchLoader instance which we first have to unwrap by using to_i + !stage.median.to_i.zero? ? distance_of_time_in_words(stage.median) : nil end end diff --git a/app/serializers/award_emoji_entity.rb b/app/serializers/award_emoji_entity.rb new file mode 100644 index 00000000000..6e03cd02392 --- /dev/null +++ b/app/serializers/award_emoji_entity.rb @@ -0,0 +1,4 @@ +class AwardEmojiEntity < Grape::Entity + expose :name + expose :user, using: API::Entities::UserSafe +end diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb index 4e6c15f673b..8cade280b0c 100644 --- a/app/serializers/base_serializer.rb +++ b/app/serializers/base_serializer.rb @@ -1,6 +1,9 @@ class BaseSerializer - def initialize(parameters = {}) - @request = EntityRequest.new(parameters) + attr_reader :params + + def initialize(params = {}) + @params = params + @request = EntityRequest.new(params) end def represent(resource, opts = {}, entity_class = nil) diff --git a/app/serializers/blob_entity.rb b/app/serializers/blob_entity.rb new file mode 100644 index 00000000000..ad039a2623d --- /dev/null +++ b/app/serializers/blob_entity.rb @@ -0,0 +1,13 @@ +class BlobEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :path, :name, :mode + + expose :icon do |blob| + IconsHelper.file_type_icon_class('file', blob.mode, blob.name) + end + + expose :url do |blob| + project_blob_path(request.project, File.join(request.ref, blob.path)) + end +end diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb index 301b718d060..f2d76a8ad81 100644 --- a/app/serializers/build_action_entity.rb +++ b/app/serializers/build_action_entity.rb @@ -6,10 +6,7 @@ class BuildActionEntity < Grape::Entity end expose :path do |build| - play_namespace_project_job_path( - build.project.namespace, - build.project, - build) + play_project_job_path(build.project, build) end expose :playable?, as: :playable diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb index cb55c98f7c6..6e0e33bc09b 100644 --- a/app/serializers/build_artifact_entity.rb +++ b/app/serializers/build_artifact_entity.rb @@ -9,24 +9,15 @@ class BuildArtifactEntity < Grape::Entity expose :artifacts_expire_at, as: :expire_at expose :path do |job| - download_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + download_project_job_artifacts_path(project, job) end expose :keep_path, if: -> (*) { job.has_expiring_artifacts? } do |job| - keep_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + keep_project_job_artifacts_path(project, job) end expose :browse_path do |job| - browse_namespace_project_job_artifacts_path( - project.namespace, - project, - job) + browse_project_job_artifacts_path(project, job) end private diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index eeb5399aa8b..69d46f5ec14 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -6,8 +6,8 @@ class BuildDetailsEntity < JobEntity expose :pipeline, using: PipelineEntity expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity - expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build| - erase_namespace_project_job_path(project.namespace, project, build) + expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build| + erase_project_job_path(project, build) end expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do @@ -16,23 +16,24 @@ class BuildDetailsEntity < JobEntity end expose :path do |build| - namespace_project_merge_request_path(project.namespace, project, build.merge_request) + project_merge_request_path(build.merge_request.project, + build.merge_request) end end expose :new_issue_path, if: -> (*) { can?(request.current_user, :create_issue, project) && build.failed? } do |build| - new_namespace_project_issue_path(project.namespace, project, issue: build_failed_issue_options) + new_project_issue_path(project, issue: build_failed_issue_options) end expose :raw_path do |build| - raw_namespace_project_job_path(project.namespace, project, build) + raw_project_job_path(project, build) end private def build_failed_issue_options - { title: "Build Failed ##{build.id}", - description: namespace_project_job_path(project.namespace, project, build) } + { title: "Job Failed ##{build.id}", + description: "Job [##{build.id}](#{project_job_path(project, build)}) failed for #{build.sha}:\n" } end def current_user diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb new file mode 100644 index 00000000000..b22a0b666ef --- /dev/null +++ b/app/serializers/cluster_application_entity.rb @@ -0,0 +1,6 @@ +class ClusterApplicationEntity < Grape::Entity + expose :name + expose :status_name, as: :status + expose :status_reason + expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) } +end diff --git a/app/serializers/cluster_entity.rb b/app/serializers/cluster_entity.rb new file mode 100644 index 00000000000..7e5b0997878 --- /dev/null +++ b/app/serializers/cluster_entity.rb @@ -0,0 +1,7 @@ +class ClusterEntity < Grape::Entity + include RequestAwareEntity + + expose :status_name, as: :status + expose :status_reason + expose :applications, using: ClusterApplicationEntity +end diff --git a/app/serializers/cluster_serializer.rb b/app/serializers/cluster_serializer.rb new file mode 100644 index 00000000000..2e13c1501e7 --- /dev/null +++ b/app/serializers/cluster_serializer.rb @@ -0,0 +1,7 @@ +class ClusterSerializer < BaseSerializer + entity ClusterEntity + + def represent_status(resource) + represent(resource, { only: [:status, :status_reason, :applications] }) + end +end diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb index 31763955f97..c8dd98cc04d 100644 --- a/app/serializers/commit_entity.rb +++ b/app/serializers/commit_entity.rb @@ -1,4 +1,4 @@ -class CommitEntity < API::Entities::RepoCommit +class CommitEntity < API::Entities::Commit include RequestAwareEntity expose :author, using: UserEntity @@ -8,16 +8,10 @@ class CommitEntity < API::Entities::RepoCommit end expose :commit_url do |commit| - namespace_project_commit_url( - request.project.namespace, - request.project, - commit) + project_commit_url(request.project, commit) end expose :commit_path do |commit| - namespace_project_commit_path( - request.project.namespace, - request.project, - commit) + project_commit_path(request.project, commit) end end diff --git a/app/serializers/concerns/with_pagination.rb b/app/serializers/concerns/with_pagination.rb new file mode 100644 index 00000000000..89631b73fcf --- /dev/null +++ b/app/serializers/concerns/with_pagination.rb @@ -0,0 +1,22 @@ +module WithPagination + attr_accessor :paginator + + def with_pagination(request, response) + tap { self.paginator = Gitlab::Serializer::Pagination.new(request, response) } + end + + def paginated? + paginator.present? + end + + # super is `BaseSerializer#represent` here. + # + # we shouldn't try to paginate single resources + def represent(resource, opts = {}) + if paginated? && resource.respond_to?(:page) + super(paginator.paginate(resource), opts) + else + super(resource, opts) + end + end +end diff --git a/app/serializers/container_repositories_serializer.rb b/app/serializers/container_repositories_serializer.rb new file mode 100644 index 00000000000..56dc70b5687 --- /dev/null +++ b/app/serializers/container_repositories_serializer.rb @@ -0,0 +1,3 @@ +class ContainerRepositoriesSerializer < BaseSerializer + entity ContainerRepositoryEntity +end diff --git a/app/serializers/container_repository_entity.rb b/app/serializers/container_repository_entity.rb new file mode 100644 index 00000000000..1103cf30a07 --- /dev/null +++ b/app/serializers/container_repository_entity.rb @@ -0,0 +1,25 @@ +class ContainerRepositoryEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :path, :location + + expose :tags_path do |repository| + project_registry_repository_tags_path(project, repository, format: :json) + end + + expose :destroy_path, if: -> (*) { can_destroy? } do |repository| + project_container_registry_path(project, repository, format: :json) + end + + private + + alias_method :repository, :object + + def project + request.project + end + + def can_destroy? + can?(request.current_user, :update_container_image, project) + end +end diff --git a/app/serializers/container_tag_entity.rb b/app/serializers/container_tag_entity.rb new file mode 100644 index 00000000000..8f1488e6cbb --- /dev/null +++ b/app/serializers/container_tag_entity.rb @@ -0,0 +1,23 @@ +class ContainerTagEntity < Grape::Entity + include RequestAwareEntity + + expose :name, :location, :revision, :short_revision, :total_size, :created_at + + expose :destroy_path, if: -> (*) { can_destroy? } do |tag| + project_registry_repository_tag_path(project, tag.repository, tag.name) + end + + private + + alias_method :tag, :object + + def project + request.project + end + + def can_destroy? + # TODO: We check permission against @project, not tag, + # as tag is no AR object that is attached to project + can?(request.current_user, :update_container_image, project) + end +end diff --git a/app/serializers/container_tags_serializer.rb b/app/serializers/container_tags_serializer.rb new file mode 100644 index 00000000000..6ff3adff135 --- /dev/null +++ b/app/serializers/container_tags_serializer.rb @@ -0,0 +1,17 @@ +class ContainerTagsSerializer < BaseSerializer + entity ContainerTagEntity + + def with_pagination(request, response) + tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) } + end + + def paginated? + @paginator.present? + end + + def represent(resource, opts = {}) + resource = @paginator.paginate(resource) if paginated? + + super(resource, opts) + end +end diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb index 068013c8829..2678f99510c 100644 --- a/app/serializers/deploy_key_entity.rb +++ b/app/serializers/deploy_key_entity.rb @@ -3,19 +3,20 @@ class DeployKeyEntity < Grape::Entity expose :user_id expose :title expose :fingerprint - expose :can_push expose :destroyed_when_orphaned?, as: :destroyed_when_orphaned expose :almost_orphaned?, as: :almost_orphaned expose :created_at expose :updated_at - expose :projects, using: ProjectEntity do |deploy_key| - deploy_key.projects.select { |project| options[:user].can?(:read_project, project) } + expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key| + deploy_key.deploy_keys_projects + .without_project_deleted + .select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) } end expose :can_edit private def can_edit - options[:user].can?(:update_deploy_key, object) + Ability.allowed?(options[:user], :update_deploy_key, object) end end diff --git a/app/serializers/deploy_keys_project_entity.rb b/app/serializers/deploy_keys_project_entity.rb new file mode 100644 index 00000000000..568ef5ab75e --- /dev/null +++ b/app/serializers/deploy_keys_project_entity.rb @@ -0,0 +1,4 @@ +class DeployKeysProjectEntity < Grape::Entity + expose :can_push + expose :project, using: ProjectEntity +end diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb index e493c9162fd..241c689bccd 100644 --- a/app/serializers/deployment_entity.rb +++ b/app/serializers/deployment_entity.rb @@ -11,10 +11,7 @@ class DeploymentEntity < Grape::Entity end expose :ref_path do |deployment| - namespace_project_tree_path( - deployment.project.namespace, - deployment.project, - id: deployment.ref) + project_tree_path(deployment.project, id: deployment.ref) end end diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb new file mode 100644 index 00000000000..6e68d275047 --- /dev/null +++ b/app/serializers/diff_file_entity.rb @@ -0,0 +1,41 @@ +class DiffFileEntity < Grape::Entity + include DiffHelper + include SubmoduleHelper + include BlobHelper + include IconsHelper + include ActionView::Helpers::TagHelper + + expose :submodule?, as: :submodule + + expose :submodule_link do |diff_file| + submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository).first + end + + expose :blob_path do |diff_file| + diff_file.blob.path + end + + expose :blob_icon do |diff_file| + blob_icon(diff_file.b_mode, diff_file.file_path) + end + + expose :file_path + expose :deleted_file?, as: :deleted_file + expose :renamed_file?, as: :renamed_file + expose :old_path + expose :new_path + expose :mode_changed?, as: :mode_changed + expose :a_mode + expose :b_mode + expose :text?, as: :text + + expose :old_path_html do |diff_file| + old_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) + old_path + end + + expose :new_path_html do |diff_file| + _, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path) + new_path + end +end diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb new file mode 100644 index 00000000000..bbbcf6a97c1 --- /dev/null +++ b/app/serializers/discussion_entity.rb @@ -0,0 +1,48 @@ +class DiscussionEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :reply_id + expose :expanded?, as: :expanded + + expose :notes, using: NoteEntity + + expose :individual_note?, as: :individual_note + expose :resolvable?, as: :resolvable + expose :resolved?, as: :resolved + expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion| + resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id) + end + expose :resolve_with_issue_path do |discussion| + new_project_issue_path(discussion.project, merge_request_to_resolve_discussions_of: discussion.noteable.iid, discussion_to_resolve: discussion.id) + end + + expose :diff_file, using: DiffFileEntity, if: -> (d, _) { defined? d.diff_file } + + expose :diff_discussion?, as: :diff_discussion + + expose :truncated_diff_lines, if: -> (d, _) { (defined? d.diff_file) && d.diff_file.text? } do |discussion| + options[:context].render_to_string( + partial: "projects/diffs/line", + collection: discussion.truncated_diff_lines, + as: :line, + locals: { diff_file: discussion.diff_file, + discussion_expanded: true, + plain: true }, + layout: false, + formats: [:html] + ) + end + + expose :image_diff_html, if: -> (d, _) { defined? d.diff_file } do |discussion| + diff_file = discussion.diff_file + partial = diff_file.new_file? || diff_file.deleted_file? ? 'single_image_diff' : 'replaced_image_diff' + options[:context].render_to_string( + partial: "projects/diffs/#{partial}", + locals: { diff_file: diff_file, + position: discussion.position.to_json, + click_to_comment: false }, + layout: false, + formats: [:html] + ) + end +end diff --git a/app/serializers/discussion_serializer.rb b/app/serializers/discussion_serializer.rb new file mode 100644 index 00000000000..ed5e1224bb2 --- /dev/null +++ b/app/serializers/discussion_serializer.rb @@ -0,0 +1,3 @@ +class DiscussionSerializer < BaseSerializer + entity DiscussionEntity +end diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb index 4e8a3c67b21..ba0ae6ba8a0 100644 --- a/app/serializers/environment_entity.rb +++ b/app/serializers/environment_entity.rb @@ -10,32 +10,24 @@ class EnvironmentEntity < Grape::Entity expose :stop_action? expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment| - metrics_namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + metrics_project_environment_path(environment.project, environment) end expose :environment_path do |environment| - namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + project_environment_path(environment.project, environment) end expose :stop_path do |environment| - stop_namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + stop_project_environment_path(environment.project, environment) end expose :terminal_path, if: ->(environment, _) { environment.has_terminals? } do |environment| can?(request.current_user, :admin_environment, environment.project) && - terminal_namespace_project_environment_path( - environment.project.namespace, - environment.project, - environment) + terminal_project_environment_path(environment.project, environment) + end + + expose :folder_path do |environment| + folder_project_environments_path(environment.project, environment.folder_name) end expose :created_at, :updated_at diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb index d0a60f134da..84722f33f59 100644 --- a/app/serializers/environment_serializer.rb +++ b/app/serializers/environment_serializer.rb @@ -1,4 +1,6 @@ class EnvironmentSerializer < BaseSerializer + include WithPagination + Item = Struct.new(:name, :size, :latest) entity EnvironmentEntity @@ -7,18 +9,10 @@ class EnvironmentSerializer < BaseSerializer tap { @itemize = true } end - def with_pagination(request, response) - tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) } - end - def itemized? @itemize end - def paginated? - @paginator.present? - end - def represent(resource, opts = {}) if itemized? itemize(resource).map do |item| @@ -27,8 +21,6 @@ class EnvironmentSerializer < BaseSerializer latest: super(item.latest, opts) } end else - resource = @paginator.paginate(resource) if paginated? - super(resource, opts) end end @@ -36,9 +28,9 @@ class EnvironmentSerializer < BaseSerializer private def itemize(resource) - items = resource.order('folder_name ASC') + items = resource.order('folder ASC') .group('COALESCE(environment_type, name)') - .select('COALESCE(environment_type, name) AS folder_name', + .select('COALESCE(environment_type, name) AS folder', 'COUNT(*) AS size', 'MAX(id) AS last_id') # It makes a difference when you call `paginate` method, because @@ -49,7 +41,7 @@ class EnvironmentSerializer < BaseSerializer environments = resource.where(id: items.map(&:last_id)).index_by(&:id) items.map do |item| - Item.new(item.folder_name, item.size, environments[item.last_id]) + Item.new(item.folder, item.size, environments[item.last_id]) end end end diff --git a/app/serializers/event_entity.rb b/app/serializers/event_entity.rb deleted file mode 100644 index 935d67a4f37..00000000000 --- a/app/serializers/event_entity.rb +++ /dev/null @@ -1,4 +0,0 @@ -class EventEntity < Grape::Entity - expose :author, using: UserEntity - expose :updated_at -end diff --git a/app/serializers/group_child_entity.rb b/app/serializers/group_child_entity.rb new file mode 100644 index 00000000000..15ec0f89bb2 --- /dev/null +++ b/app/serializers/group_child_entity.rb @@ -0,0 +1,97 @@ +class GroupChildEntity < Grape::Entity + include ActionView::Helpers::NumberHelper + include RequestAwareEntity + include MarkupHelper + + expose :id, :name, :description, :visibility, :full_name, + :created_at, :updated_at, :avatar_url + + expose :type do |instance| + type + end + + expose :can_edit do |instance| + can_edit? + end + + expose :edit_path do |instance| + # We know `type` will be one either `project` or `group`. + # The `edit_polymorphic_path` helper would try to call the path helper + # with a plural: `edit_groups_path(instance)` or `edit_projects_path(instance)` + # while our methods are `edit_group_path` or `edit_group_path` + public_send("edit_#{type}_path", instance) # rubocop:disable GitlabSecurity/PublicSend + end + + expose :relative_path do |instance| + polymorphic_path(instance) + end + + expose :permission do |instance| + membership&.human_access + end + + # Project only attributes + expose :star_count, + if: lambda { |_instance, _options| project? } + + # Group only attributes + expose :children_count, :parent_id, :project_count, :subgroup_count, + unless: lambda { |_instance, _options| project? } + + expose :leave_path, unless: lambda { |_instance, _options| project? } do |instance| + leave_group_members_path(instance) + end + + expose :can_leave, unless: lambda { |_instance, _options| project? } do |instance| + if membership + can?(request.current_user, :destroy_group_member, membership) + else + false + end + end + + expose :number_projects_with_delimiter, unless: lambda { |_instance, _options| project? } do |instance| + number_with_delimiter(instance.project_count) + end + + expose :number_users_with_delimiter, unless: lambda { |_instance, _options| project? } do |instance| + number_with_delimiter(instance.member_count) + end + + expose :markdown_description do |instance| + markdown_description + end + + private + + def membership + return unless request.current_user + + @membership ||= request.current_user.members.find_by(source: object) + end + + def project? + object.is_a?(Project) + end + + def type + object.class.name.downcase + end + + def markdown_description + markdown_field(object, :description) + end + + def can_edit? + return false unless request.respond_to?(:current_user) + + if project? + # Avoid checking rights for each project, as it might be expensive if the + # user cannot read cross project. + can?(request.current_user, :read_cross_project) && + can?(request.current_user, :admin_project, object) + else + can?(request.current_user, :admin_group, object) + end + end +end diff --git a/app/serializers/group_child_serializer.rb b/app/serializers/group_child_serializer.rb new file mode 100644 index 00000000000..2baef0a5703 --- /dev/null +++ b/app/serializers/group_child_serializer.rb @@ -0,0 +1,51 @@ +class GroupChildSerializer < BaseSerializer + include WithPagination + + attr_reader :hierarchy_root, :should_expand_hierarchy + + entity GroupChildEntity + + def expand_hierarchy(hierarchy_root = nil) + @hierarchy_root = hierarchy_root + @should_expand_hierarchy = true + + self + end + + def represent(resource, opts = {}, entity_class = nil) + if should_expand_hierarchy + paginator.paginate(resource) if paginated? + represent_hierarchies(resource, opts) + else + super(resource, opts) + end + end + + protected + + def represent_hierarchies(children, opts) + if children.is_a?(GroupDescendant) + represent_hierarchy(children.hierarchy(hierarchy_root), opts).first + else + hierarchies = GroupDescendant.build_hierarchy(children, hierarchy_root) + # When an array was passed, we always want to represent an array. + # Even if the hierarchy only contains one element + represent_hierarchy(Array.wrap(hierarchies), opts) + end + end + + def represent_hierarchy(hierarchy, opts) + serializer = self.class.new(params) + + if hierarchy.is_a?(Hash) + hierarchy.map do |parent, children| + serializer.represent(parent, opts) + .merge(children: Array.wrap(serializer.represent_hierarchy(children, opts))) + end + elsif hierarchy.is_a?(Array) + hierarchy.flat_map { |child| serializer.represent_hierarchy(child, opts) } + else + serializer.represent(hierarchy, opts) + end + end +end diff --git a/app/serializers/group_entity.rb b/app/serializers/group_entity.rb index 7c872a3e986..6d8466da902 100644 --- a/app/serializers/group_entity.rb +++ b/app/serializers/group_entity.rb @@ -45,6 +45,6 @@ class GroupEntity < Grape::Entity end expose :avatar_url do |group| - group_icon(group) + group_icon_url(group) end end diff --git a/app/serializers/group_serializer.rb b/app/serializers/group_serializer.rb index 26e8566828b..8cf7eb63bcf 100644 --- a/app/serializers/group_serializer.rb +++ b/app/serializers/group_serializer.rb @@ -1,19 +1,5 @@ class GroupSerializer < BaseSerializer - entity GroupEntity - - def with_pagination(request, response) - tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) } - end + include WithPagination - def paginated? - @paginator.present? - end - - def represent(resource, opts = {}) - if paginated? - super(@paginator.paginate(resource), opts) - else - super(resource, opts) - end - end + entity GroupEntity end diff --git a/app/serializers/group_variable_entity.rb b/app/serializers/group_variable_entity.rb new file mode 100644 index 00000000000..62cf0b21e1e --- /dev/null +++ b/app/serializers/group_variable_entity.rb @@ -0,0 +1,7 @@ +class GroupVariableEntity < Grape::Entity + expose :id + expose :key + expose :value + + expose :protected?, as: :protected +end diff --git a/app/serializers/group_variable_serializer.rb b/app/serializers/group_variable_serializer.rb new file mode 100644 index 00000000000..8f8205924aa --- /dev/null +++ b/app/serializers/group_variable_serializer.rb @@ -0,0 +1,3 @@ +class GroupVariableSerializer < BaseSerializer + entity GroupVariableEntity +end diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb index bd5211b8e58..6f31fbd6b7c 100644 --- a/app/serializers/issuable_entity.rb +++ b/app/serializers/issuable_entity.rb @@ -1,18 +1,8 @@ class IssuableEntity < Grape::Entity + include RequestAwareEntity + expose :id expose :iid - expose :author_id expose :description - expose :lock_version - expose :milestone_id - expose :state expose :title - expose :updated_by_id - expose :created_at - expose :updated_at - expose :deleted_at - expose :time_estimate - expose :total_time_spent - expose :human_time_estimate - expose :human_total_time_spent end diff --git a/app/serializers/issuable_sidebar_entity.rb b/app/serializers/issuable_sidebar_entity.rb new file mode 100644 index 00000000000..29138c803df --- /dev/null +++ b/app/serializers/issuable_sidebar_entity.rb @@ -0,0 +1,12 @@ +class IssuableSidebarEntity < Grape::Entity + include TimeTrackableEntity + include RequestAwareEntity + + expose :participants, using: ::API::Entities::UserBasic do |issuable| + issuable.participants(request.current_user) + end + + expose :subscribed do |issuable| + issuable.subscribed?(request.current_user, issuable.project) + end +end diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index 35df95549b7..b5e2334b6e3 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -1,16 +1,41 @@ class IssueEntity < IssuableEntity - include RequestAwareEntity + include TimeTrackableEntity - expose :branch_name + expose :state + expose :milestone_id + expose :updated_by_id + expose :created_at + expose :updated_at + expose :milestone, using: API::Entities::Milestone + expose :labels, using: LabelEntity + expose :lock_version + expose :author_id expose :confidential + expose :discussion_locked expose :assignees, using: API::Entities::UserBasic expose :due_date expose :moved_to_id expose :project_id - expose :milestone, using: API::Entities::Milestone - expose :labels, using: LabelEntity expose :web_url do |issue| - namespace_project_issue_path(issue.project.namespace, issue.project, issue) + project_issue_path(issue.project, issue) + end + + expose :current_user do + expose :can_create_note do |issue| + can?(request.current_user, :create_note, issue) + end + + expose :can_update do |issue| + can?(request.current_user, :update_issue, issue) + end + end + + expose :create_note_path do |issue| + project_notes_path(issue.project, target_type: 'issue', target_id: issue.id) + end + + expose :preview_note_path do |issue| + preview_markdown_path(issue.project, quick_actions_target_type: 'Issue', quick_actions_target_id: issue.id) end end diff --git a/app/serializers/issue_serializer.rb b/app/serializers/issue_serializer.rb index 4fff54a9126..2555595379b 100644 --- a/app/serializers/issue_serializer.rb +++ b/app/serializers/issue_serializer.rb @@ -1,3 +1,16 @@ class IssueSerializer < BaseSerializer - entity IssueEntity + # This overrided method takes care of which entity should be used + # to serialize the `issue` based on `basic` key in `opts` param. + # Hence, `entity` doesn't need to be declared on the class scope. + def represent(merge_request, opts = {}) + entity = + case opts[:serializer] + when 'sidebar' + IssueSidebarEntity + else + IssueEntity + end + + super(merge_request, opts, entity) + end end diff --git a/app/serializers/issue_sidebar_entity.rb b/app/serializers/issue_sidebar_entity.rb new file mode 100644 index 00000000000..6c823dbfe95 --- /dev/null +++ b/app/serializers/issue_sidebar_entity.rb @@ -0,0 +1,3 @@ +class IssueSidebarEntity < IssuableSidebarEntity + expose :assignees, using: API::Entities::UserBasic +end diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index d6de43bcbcb..523b522d449 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -4,6 +4,8 @@ class JobEntity < Grape::Entity expose :id expose :name + expose :started?, as: :started + expose :build_path do |build| build.target_url || path_to(:namespace_project_job, build) end @@ -46,6 +48,6 @@ class JobEntity < Grape::Entity end def path_to(route, build) - send("#{route}_path", build.project.namespace, build.project, build) + send("#{route}_path", build.project.namespace, build.project, build) # rubocop:disable GitlabSecurity/PublicSend end end diff --git a/app/serializers/label_entity.rb b/app/serializers/label_entity.rb index ad565654342..4452161051e 100644 --- a/app/serializers/label_entity.rb +++ b/app/serializers/label_entity.rb @@ -1,5 +1,6 @@ class LabelEntity < Grape::Entity - expose :id + expose :id, if: ->(label, _) { !label.is_a?(GlobalLabel) } + expose :title expose :color expose :description diff --git a/app/serializers/lfs_file_lock_entity.rb b/app/serializers/lfs_file_lock_entity.rb new file mode 100644 index 00000000000..264a77adc3f --- /dev/null +++ b/app/serializers/lfs_file_lock_entity.rb @@ -0,0 +1,11 @@ +class LfsFileLockEntity < Grape::Entity + root 'locks', 'lock' + + expose :path + expose(:id) { |entity| entity.id.to_s } + expose(:created_at, as: :locked_at) { |entity| entity.created_at.to_s(:iso8601) } + + expose :owner do + expose(:name) { |entity| entity.user&.name } + end +end diff --git a/app/serializers/lfs_file_lock_serializer.rb b/app/serializers/lfs_file_lock_serializer.rb new file mode 100644 index 00000000000..ba8fb1a461d --- /dev/null +++ b/app/serializers/lfs_file_lock_serializer.rb @@ -0,0 +1,3 @@ +class LfsFileLockSerializer < BaseSerializer + entity LfsFileLockEntity +end diff --git a/app/serializers/merge_request_basic_entity.rb b/app/serializers/merge_request_basic_entity.rb index 8461f158bb5..e4aec977f01 100644 --- a/app/serializers/merge_request_basic_entity.rb +++ b/app/serializers/merge_request_basic_entity.rb @@ -1,11 +1,8 @@ -class MergeRequestBasicEntity < Grape::Entity +class MergeRequestBasicEntity < IssuableSidebarEntity expose :assignee_id expose :merge_status expose :merge_error expose :state expose :source_branch_exists?, as: :source_branch_exists - expose :time_estimate - expose :total_time_spent - expose :human_time_estimate - expose :human_total_time_spent + expose :rebase_in_progress?, as: :rebase_in_progress end diff --git a/app/serializers/merge_request_metrics_entity.rb b/app/serializers/merge_request_metrics_entity.rb new file mode 100644 index 00000000000..3548107ac16 --- /dev/null +++ b/app/serializers/merge_request_metrics_entity.rb @@ -0,0 +1,6 @@ +class MergeRequestMetricsEntity < Grape::Entity + expose :latest_closed_at, as: :closed_at + expose :merged_at + expose :latest_closed_by, as: :closed_by, using: UserEntity + expose :merged_by, using: UserEntity +end diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb index f67034ce47a..caf193bdae3 100644 --- a/app/serializers/merge_request_serializer.rb +++ b/app/serializers/merge_request_serializer.rb @@ -1,9 +1,16 @@ class MergeRequestSerializer < BaseSerializer # This overrided method takes care of which entity should be used - # to serialize the `merge_request` based on `basic` key in `opts` param. + # to serialize the `merge_request` based on `serializer` key in `opts` param. # Hence, `entity` doesn't need to be declared on the class scope. def represent(merge_request, opts = {}) - entity = opts[:basic] ? MergeRequestBasicEntity : MergeRequestEntity + entity = + case opts[:serializer] + when 'basic', 'sidebar' + MergeRequestBasicEntity + else # It's 'widget' + MergeRequestWidgetEntity + end + super(merge_request, opts, entity) end end diff --git a/app/serializers/merge_request_entity.rb b/app/serializers/merge_request_widget_entity.rb index 7bb981041cc..4a812e39ee1 100644 --- a/app/serializers/merge_request_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -1,8 +1,6 @@ -class MergeRequestEntity < IssuableEntity - include RequestAwareEntity - +class MergeRequestWidgetEntity < IssuableEntity + expose :state expose :in_progress_merge_commit_sha - expose :locked_at expose :merge_commit_sha expose :merge_error expose :merge_params @@ -13,32 +11,62 @@ class MergeRequestEntity < IssuableEntity expose :source_project_id expose :target_branch expose :target_project_id + expose :allow_maintainer_to_push + + expose :should_be_rebased?, as: :should_be_rebased + expose :ff_only_enabled do |merge_request| + merge_request.project.merge_requests_ff_only_enabled + end + + expose :metrics do |merge_request| + metrics = build_metrics(merge_request) - # Events - expose :merge_event, using: EventEntity - expose :closed_event, using: EventEntity + MergeRequestMetricsEntity.new(metrics).as_json + end + + expose :rebase_commit_sha + expose :rebase_in_progress?, as: :rebase_in_progress + + expose :can_push_to_source_branch do |merge_request| + presenter(merge_request).can_push_to_source_branch? + end + + expose :rebase_path do |merge_request| + presenter(merge_request).rebase_path + end # User entities - expose :author, using: UserEntity expose :merge_user, using: UserEntity # Diff sha's expose :diff_head_sha do |merge_request| - merge_request.diff_head_sha if merge_request.diff_head_commit + merge_request.diff_head_sha.presence end - expose :merge_commit_sha expose :merge_commit_message - expose :head_pipeline, with: PipelineDetailsEntity, as: :pipeline + expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline # Booleans + expose :merge_ongoing?, as: :merge_ongoing expose :work_in_progress?, as: :work_in_progress expose :source_branch_exists?, as: :source_branch_exists - expose :mergeable_discussions_state?, as: :mergeable_discussions_state + + expose :mergeable_discussions_state?, as: :mergeable_discussions_state do |merge_request| + # This avoids calling MergeRequest#mergeable_discussions_state without + # considering the state of the MR first. If a MR isn't mergeable, we can + # safely short-circuit it. + if merge_request.mergeable_state?(skip_ci_check: true, skip_discussions_check: true) + merge_request.mergeable_discussions_state? + else + false + end + end + expose :branch_missing?, as: :branch_missing expose :commits_count expose :cannot_be_merged?, as: :has_conflicts expose :can_be_merged?, as: :can_be_merged + expose :mergeable?, as: :mergeable expose :remove_source_branch?, as: :remove_source_branch expose :project_archived do |merge_request| @@ -89,6 +117,14 @@ class MergeRequestEntity < IssuableEntity expose :can_cherry_pick_on_current_merge_request do |merge_request| presenter(merge_request).can_cherry_pick_on_current_merge_request? end + + expose :can_create_note do |issue| + can?(request.current_user, :create_note, issue.project) + end + + expose :can_update do |issue| + can?(request.current_user, :update_issue, issue) + end end # Paths @@ -97,11 +133,13 @@ class MergeRequestEntity < IssuableEntity presenter(merge_request).target_branch_commits_path end + expose :target_branch_tree_path do |merge_request| + presenter(merge_request).target_branch_tree_path + end + expose :new_blob_path do |merge_request| - if can?(current_user, :push_code, merge_request.project) - namespace_project_new_blob_path(merge_request.project.namespace, - merge_request.project, - merge_request.source_branch) + if presenter(merge_request).can_push_to_source_branch? + project_new_blob_path(merge_request.source_project, merge_request.source_branch) end end @@ -134,30 +172,19 @@ class MergeRequestEntity < IssuableEntity end expose :email_patches_path do |merge_request| - namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request, - format: :patch) + project_merge_request_path(merge_request.project, merge_request, format: :patch) end expose :plain_diff_path do |merge_request| - namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request, - format: :diff) + project_merge_request_path(merge_request.project, merge_request, format: :diff) end expose :status_path do |merge_request| - namespace_project_merge_request_path(merge_request.target_project.namespace, - merge_request.target_project, - merge_request, - format: :json) + project_merge_request_path(merge_request.target_project, merge_request, format: :json) end expose :ci_environments_status_path do |merge_request| - ci_environments_status_namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request) + ci_environments_status_project_merge_request_path(merge_request.project, merge_request) end expose :merge_commit_message_with_description do |merge_request| @@ -172,10 +199,12 @@ class MergeRequestEntity < IssuableEntity end end + expose :create_note_path do |merge_request| + project_notes_path(merge_request.project, target_type: 'merge_request', target_id: merge_request.id) + end + expose :commit_change_content_path do |merge_request| - commit_change_content_namespace_project_merge_request_path(merge_request.project.namespace, - merge_request.project, - merge_request) + commit_change_content_project_merge_request_path(merge_request.project, merge_request) end private @@ -186,4 +215,27 @@ class MergeRequestEntity < IssuableEntity @presenters ||= {} @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) end + + # Once SchedulePopulateMergeRequestMetricsWithEventsData fully runs, + # we can remove this method and just serialize MergeRequest#metrics + # instead. See https://gitlab.com/gitlab-org/gitlab-ce/issues/41587 + def build_metrics(merge_request) + # There's no need to query and serialize metrics data for merge requests that are not + # merged or closed. + return unless merge_request.merged? || merge_request.closed? + return merge_request.metrics if merge_request.merged? && merge_request.metrics&.merged_by_id + return merge_request.metrics if merge_request.closed? && merge_request.metrics&.latest_closed_by_id + + build_metrics_from_events(merge_request) + end + + def build_metrics_from_events(merge_request) + closed_event = merge_request.closed_event + merge_event = merge_request.merge_event + + MergeRequest::Metrics.new(latest_closed_at: closed_event&.updated_at, + latest_closed_by: closed_event&.author, + merged_at: merge_event&.updated_at, + merged_by: merge_event&.author) + end end diff --git a/app/serializers/note_attachment_entity.rb b/app/serializers/note_attachment_entity.rb new file mode 100644 index 00000000000..1ad50568ab9 --- /dev/null +++ b/app/serializers/note_attachment_entity.rb @@ -0,0 +1,5 @@ +class NoteAttachmentEntity < Grape::Entity + expose :url + expose :filename + expose :image?, as: :image +end diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb new file mode 100644 index 00000000000..4ccf0bca476 --- /dev/null +++ b/app/serializers/note_entity.rb @@ -0,0 +1,72 @@ +class NoteEntity < API::Entities::Note + include RequestAwareEntity + + expose :type + + expose :author, using: NoteUserEntity + + expose :human_access do |note| + note.project.team.human_max_access(note.author_id) + end + + unexpose :note, as: :body + expose :note + + expose :redacted_note_html, as: :note_html + + expose :last_edited_at, if: -> (note, _) { note.edited? } + expose :last_edited_by, using: NoteUserEntity, if: -> (note, _) { note.edited? } + + expose :current_user do + expose :can_edit do |note| + Ability.can_edit_note?(request.current_user, note) + end + end + + expose :resolved?, as: :resolved + expose :resolvable?, as: :resolvable + expose :resolved_by, using: NoteUserEntity + + expose :system_note_icon_name, if: -> (note, _) { note.system? } do |note| + SystemNoteHelper.system_note_icon_name(note) + end + + expose :discussion_id do |note| + note.discussion_id(request.noteable) + end + + expose :emoji_awardable?, as: :emoji_awardable + expose :award_emoji, if: -> (note, _) { note.emoji_awardable? }, using: AwardEmojiEntity + expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note| + if note.for_personal_snippet? + toggle_award_emoji_snippet_note_path(note.noteable, note) + else + toggle_award_emoji_project_note_path(note.project, note.id) + end + end + + expose :report_abuse_path do |note| + new_abuse_report_path(user_id: note.author.id, ref_url: Gitlab::UrlBuilder.build(note)) + end + + expose :path do |note| + if note.for_personal_snippet? + snippet_note_path(note.noteable, note) + else + project_note_path(note.project, note) + end + end + + expose :resolve_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note| + resolve_project_merge_request_discussion_path(note.project, note.noteable, note.discussion_id) + end + + expose :resolve_with_issue_path, if: -> (note, _) { note.part_of_discussion? && note.resolvable? } do |note| + new_project_issue_path(note.project, merge_request_to_resolve_discussions_of: note.noteable.iid, discussion_to_resolve: note.discussion_id) + end + + expose :attachment, using: NoteAttachmentEntity, if: -> (note, _) { note.attachment? } + expose :delete_attachment_path, if: -> (note, _) { note.attachment? } do |note| + delete_attachment_project_note_path(note.project, note) + end +end diff --git a/app/serializers/note_serializer.rb b/app/serializers/note_serializer.rb new file mode 100644 index 00000000000..2afe40d7a34 --- /dev/null +++ b/app/serializers/note_serializer.rb @@ -0,0 +1,3 @@ +class NoteSerializer < BaseSerializer + entity NoteEntity +end diff --git a/app/serializers/note_user_entity.rb b/app/serializers/note_user_entity.rb new file mode 100644 index 00000000000..7289f3a0222 --- /dev/null +++ b/app/serializers/note_user_entity.rb @@ -0,0 +1,3 @@ +class NoteUserEntity < UserEntity + unexpose :web_url +end diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 6d1fd9d459f..6457294b285 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -10,18 +10,17 @@ class PipelineEntity < Grape::Entity expose :created_at, :updated_at expose :path do |pipeline| - namespace_project_pipeline_path( - pipeline.project.namespace, - pipeline.project, - pipeline) + project_pipeline_path(pipeline.project, pipeline) end expose :flags do expose :latest?, as: :latest expose :stuck?, as: :stuck + expose :auto_devops_source?, as: :auto_devops expose :has_yaml_errors?, as: :yaml_errors expose :can_retry?, as: :retryable expose :can_cancel?, as: :cancelable + expose :failure_reason?, as: :failure_reason end expose :details do @@ -46,21 +45,20 @@ class PipelineEntity < Grape::Entity end expose :commit, using: CommitEntity + expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? } + + expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } do |pipeline| + pipeline.present.failure_reason + end expose :retry_path, if: -> (*) { can_retry? } do |pipeline| - retry_namespace_project_pipeline_path(pipeline.project.namespace, - pipeline.project, - pipeline.id) + retry_project_pipeline_path(pipeline.project, pipeline) end expose :cancel_path, if: -> (*) { can_cancel? } do |pipeline| - cancel_namespace_project_pipeline_path(pipeline.project.namespace, - pipeline.project, - pipeline.id) + cancel_project_pipeline_path(pipeline.project, pipeline) end - expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? } - private alias_method :pipeline, :object diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 661bf17983c..7181f8a6b04 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,16 +1,10 @@ class PipelineSerializer < BaseSerializer + include WithPagination + InvalidResourceError = Class.new(StandardError) entity PipelineDetailsEntity - def with_pagination(request, response) - tap { @paginator = Gitlab::Serializer::Pagination.new(request, response) } - end - - def paginated? - @paginator.present? - end - def represent(resource, opts = {}) if resource.is_a?(ActiveRecord::Relation) diff --git a/app/serializers/project_entity.rb b/app/serializers/project_entity.rb index a471a7e6a88..b3e5fd21e97 100644 --- a/app/serializers/project_entity.rb +++ b/app/serializers/project_entity.rb @@ -1,11 +1,11 @@ class ProjectEntity < Grape::Entity include RequestAwareEntity - + expose :id expose :name expose :full_path do |project| - namespace_project_path(project.namespace, project) + project_path(project) end expose :full_name do |project| diff --git a/app/serializers/project_serializer.rb b/app/serializers/project_serializer.rb new file mode 100644 index 00000000000..74de1e79a8f --- /dev/null +++ b/app/serializers/project_serializer.rb @@ -0,0 +1,3 @@ +class ProjectSerializer < BaseSerializer + entity ProjectEntity +end diff --git a/app/serializers/runner_entity.rb b/app/serializers/runner_entity.rb index ed7dacc2dbd..e9999a36d8a 100644 --- a/app/serializers/runner_entity.rb +++ b/app/serializers/runner_entity.rb @@ -5,7 +5,7 @@ class RunnerEntity < Grape::Entity expose :edit_path, if: -> (*) { can?(request.current_user, :admin_build, project) && runner.specific? } do |runner| - edit_namespace_project_runner_path(project.namespace, project, runner) + edit_project_runner_path(project, runner) end private diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb index cee0089056f..4523b15152e 100644 --- a/app/serializers/stage_entity.rb +++ b/app/serializers/stage_entity.rb @@ -14,16 +14,14 @@ class StageEntity < Grape::Entity expose :detailed_status, as: :status, with: StatusEntity expose :path do |stage| - namespace_project_pipeline_path( - stage.pipeline.project.namespace, + project_pipeline_path( stage.pipeline.project, stage.pipeline, anchor: stage.name) end expose :dropdown_path do |stage| - stage_namespace_project_pipeline_path( - stage.pipeline.project.namespace, + stage_project_pipeline_path( stage.pipeline.project, stage.pipeline, stage: stage.name, diff --git a/app/serializers/submodule_entity.rb b/app/serializers/submodule_entity.rb new file mode 100644 index 00000000000..ed1f1ae0ef0 --- /dev/null +++ b/app/serializers/submodule_entity.rb @@ -0,0 +1,23 @@ +class SubmoduleEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :path, :name, :mode + + expose :icon do |blob| + 'archive' + end + + expose :url do |blob| + submodule_links(blob, request).first + end + + expose :tree_url do |blob| + submodule_links(blob, request).last + end + + private + + def submodule_links(blob, request) + @submodule_links ||= SubmoduleHelper.submodule_links(blob, request.ref, request.repository) + end +end diff --git a/app/serializers/time_trackable_entity.rb b/app/serializers/time_trackable_entity.rb new file mode 100644 index 00000000000..e81cd7bec72 --- /dev/null +++ b/app/serializers/time_trackable_entity.rb @@ -0,0 +1,11 @@ +module TimeTrackableEntity + extend ActiveSupport::Concern + extend Grape + + included do + expose :time_estimate + expose :total_time_spent + expose :human_time_estimate + expose :human_total_time_spent + end +end diff --git a/app/serializers/tree_entity.rb b/app/serializers/tree_entity.rb new file mode 100644 index 00000000000..9f1b485347f --- /dev/null +++ b/app/serializers/tree_entity.rb @@ -0,0 +1,13 @@ +class TreeEntity < Grape::Entity + include RequestAwareEntity + + expose :id, :path, :name, :mode + + expose :icon do |tree| + IconsHelper.file_type_icon_class('folder', tree.mode, tree.name) + end + + expose :url do |tree| + project_tree_path(request.project, File.join(request.ref, tree.path)) + end +end diff --git a/app/serializers/tree_root_entity.rb b/app/serializers/tree_root_entity.rb new file mode 100644 index 00000000000..496f070ddbd --- /dev/null +++ b/app/serializers/tree_root_entity.rb @@ -0,0 +1,25 @@ +# TODO: Inherit from TreeEntity, when `Tree` implements `id` and `name` like `Gitlab::Git::Tree`. +class TreeRootEntity < Grape::Entity + include RequestAwareEntity + + expose :path + + expose :trees, using: TreeEntity + expose :blobs, using: BlobEntity + expose :submodules, using: SubmoduleEntity + + expose :parent_tree_url do |tree| + path = tree.path.sub(%r{\A/}, '') + next unless path.present? + + path_segments = path.split('/') + path_segments.pop + parent_tree_path = path_segments.join('/') + + project_tree_path(request.project, File.join(request.ref, parent_tree_path)) + end + + expose :last_commit_path do |tree| + logs_file_project_ref_path(request.project, request.ref, tree.path) + end +end diff --git a/app/serializers/tree_serializer.rb b/app/serializers/tree_serializer.rb new file mode 100644 index 00000000000..713ade23bc9 --- /dev/null +++ b/app/serializers/tree_serializer.rb @@ -0,0 +1,3 @@ +class TreeSerializer < BaseSerializer + entity TreeRootEntity +end diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb new file mode 100644 index 00000000000..49a71ebac61 --- /dev/null +++ b/app/serializers/user_serializer.rb @@ -0,0 +1,3 @@ +class UserSerializer < BaseSerializer + entity UserEntity +end diff --git a/app/serializers/variable_entity.rb b/app/serializers/variable_entity.rb new file mode 100644 index 00000000000..d576745c073 --- /dev/null +++ b/app/serializers/variable_entity.rb @@ -0,0 +1,7 @@ +class VariableEntity < Grape::Entity + expose :id + expose :key + expose :value + + expose :protected?, as: :protected +end diff --git a/app/serializers/variable_serializer.rb b/app/serializers/variable_serializer.rb new file mode 100644 index 00000000000..32ae82ab51c --- /dev/null +++ b/app/serializers/variable_serializer.rb @@ -0,0 +1,3 @@ +class VariableSerializer < BaseSerializer + entity VariableEntity +end |