summaryrefslogtreecommitdiff
path: root/app/presenters
diff options
context:
space:
mode:
Diffstat (limited to 'app/presenters')
-rw-r--r--app/presenters/alert_management/alert_presenter.rb101
-rw-r--r--app/presenters/alert_management/prometheus_alert_presenter.rb27
-rw-r--r--app/presenters/ci/pipeline_presenter.rb11
-rw-r--r--app/presenters/clusterable_presenter.rb20
-rw-r--r--app/presenters/clusters/cluster_presenter.rb42
-rw-r--r--app/presenters/group_clusterable_presenter.rb4
-rw-r--r--app/presenters/instance_clusterable_presenter.rb4
-rw-r--r--app/presenters/merge_request_presenter.rb18
-rw-r--r--app/presenters/packages/composer/packages_presenter.rb71
-rw-r--r--app/presenters/packages/conan/package_presenter.rb114
-rw-r--r--app/presenters/packages/detail/package_presenter.rb75
-rw-r--r--app/presenters/packages/go/module_version_presenter.rb19
-rw-r--r--app/presenters/packages/npm/package_presenter.rb87
-rw-r--r--app/presenters/packages/nuget/package_metadata_presenter.rb25
-rw-r--r--app/presenters/packages/nuget/packages_metadata_presenter.rb63
-rw-r--r--app/presenters/packages/nuget/packages_versions_presenter.rb15
-rw-r--r--app/presenters/packages/nuget/presenter_helpers.rb113
-rw-r--r--app/presenters/packages/nuget/search_results_presenter.rb56
-rw-r--r--app/presenters/packages/nuget/service_index_presenter.rb85
-rw-r--r--app/presenters/packages/pypi/package_presenter.rb75
-rw-r--r--app/presenters/project_clusterable_presenter.rb4
-rw-r--r--app/presenters/project_presenter.rb2
-rw-r--r--app/presenters/projects/prometheus/alert_presenter.rb38
-rw-r--r--app/presenters/release_presenter.rb14
-rw-r--r--app/presenters/snippet_blob_presenter.rb14
25 files changed, 1060 insertions, 37 deletions
diff --git a/app/presenters/alert_management/alert_presenter.rb b/app/presenters/alert_management/alert_presenter.rb
new file mode 100644
index 00000000000..a515c70152d
--- /dev/null
+++ b/app/presenters/alert_management/alert_presenter.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module AlertManagement
+ class AlertPresenter < Gitlab::View::Presenter::Delegated
+ include Gitlab::Utils::StrongMemoize
+ include IncidentManagement::Settings
+
+ MARKDOWN_LINE_BREAK = " \n".freeze
+
+ def initialize(alert, _attributes = {})
+ super
+
+ @alert = alert
+ @project = alert.project
+ end
+
+ def issue_description
+ horizontal_line = "\n\n---\n\n"
+
+ [
+ issue_summary_markdown,
+ alert_markdown,
+ incident_management_setting.issue_template_content
+ ].compact.join(horizontal_line)
+ end
+
+ def start_time
+ started_at&.strftime('%d %B %Y, %-l:%M%p (%Z)')
+ end
+
+ def issue_summary_markdown
+ <<~MARKDOWN.chomp
+ #### Summary
+
+ #{metadata_list}
+ #{alert_details}#{metric_embed_for_alert}
+ MARKDOWN
+ end
+
+ def metrics_dashboard_url; end
+
+ private
+
+ attr_reader :alert, :project
+
+ def alerting_alert
+ strong_memoize(:alerting_alert) do
+ Gitlab::Alerting::Alert.new(project: project, payload: alert.payload).present
+ end
+ end
+
+ def alert_markdown; end
+
+ def metadata_list
+ metadata = []
+
+ metadata << list_item('Start time', start_time)
+ metadata << list_item('Severity', severity)
+ metadata << list_item('full_query', backtick(full_query)) if full_query
+ metadata << list_item('Service', service) if service
+ metadata << list_item('Monitoring tool', monitoring_tool) if monitoring_tool
+ metadata << list_item('Hosts', host_links) if hosts.any?
+ metadata << list_item('Description', description) if description.present?
+
+ metadata.join(MARKDOWN_LINE_BREAK)
+ end
+
+ def alert_details
+ if details.present?
+ <<~MARKDOWN.chomp
+
+ #### Alert Details
+
+ #{details_list}
+ MARKDOWN
+ end
+ end
+
+ def details_list
+ alert.details
+ .map { |label, value| list_item(label, value) }
+ .join(MARKDOWN_LINE_BREAK)
+ end
+
+ def metric_embed_for_alert; end
+
+ def full_query; end
+
+ def list_item(key, value)
+ "**#{key}:** #{value}".strip
+ end
+
+ def backtick(value)
+ "`#{value}`"
+ end
+
+ def host_links
+ hosts.join(' ')
+ end
+ end
+end
diff --git a/app/presenters/alert_management/prometheus_alert_presenter.rb b/app/presenters/alert_management/prometheus_alert_presenter.rb
new file mode 100644
index 00000000000..3bcc98e6784
--- /dev/null
+++ b/app/presenters/alert_management/prometheus_alert_presenter.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module AlertManagement
+ class PrometheusAlertPresenter < AlertManagement::AlertPresenter
+ def metrics_dashboard_url
+ alerting_alert.metrics_dashboard_url
+ end
+
+ private
+
+ def alert_markdown
+ alerting_alert.alert_markdown
+ end
+
+ def details_list
+ alerting_alert.annotation_list
+ end
+
+ def metric_embed_for_alert
+ alerting_alert.metric_embed_for_alert
+ end
+
+ def full_query
+ alerting_alert.full_query
+ end
+ end
+end
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 395eaeea8de..da610f13899 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -110,6 +110,17 @@ module Ci
merge_request_presenter&.target_branch_link
end
+ def downloadable_path_for_report_type(file_type)
+ if (job_artifact = batch_lookup_report_artifact_for_file_type(file_type)) &&
+ can?(current_user, :read_build, job_artifact.job)
+ download_project_job_artifacts_path(
+ job_artifact.project,
+ job_artifact.job,
+ file_type: file_type,
+ proxy: true)
+ end
+ end
+
private
def plain_ref_name
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 5e669ff2e50..efb3cf7f348 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -13,8 +13,7 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
end
def can_add_cluster?
- can?(current_user, :add_cluster, clusterable) &&
- (has_no_clusters? || multiple_clusters_available?)
+ can?(current_user, :add_cluster, clusterable)
end
def can_create_cluster?
@@ -65,7 +64,11 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError
end
- # Will be overidden in EE
+ def metrics_dashboard_path(cluster)
+ raise NotImplementedError
+ end
+
+ # Will be overridden in EE
def environments_cluster_path(cluster)
nil
end
@@ -81,17 +84,6 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
def learn_more_link
raise NotImplementedError
end
-
- private
-
- # Overridden on EE module
- def multiple_clusters_available?
- false
- end
-
- def has_no_clusters?
- clusterable.clusters.empty?
- end
end
ClusterablePresenter.prepend_if_ee('EE::ClusterablePresenter')
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index c4e3393cac9..c0da5310ca4 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -2,6 +2,7 @@
module Clusters
class ClusterPresenter < Gitlab::View::Presenter::Delegated
+ include ::Gitlab::Utils::StrongMemoize
include ActionView::Helpers::SanitizeHelper
include ActionView::Helpers::UrlHelper
include IconsHelper
@@ -60,12 +61,53 @@ module Clusters
end
end
+ def gitlab_managed_apps_logs_path
+ return unless logs_project && can_read_cluster?
+
+ if cluster.application_elastic_stack&.available?
+ elasticsearch_project_logs_path(logs_project, cluster_id: cluster.id, format: :json)
+ else
+ k8s_project_logs_path(logs_project, cluster_id: cluster.id, format: :json)
+ end
+ end
+
def read_only_kubernetes_platform_fields?
!cluster.provided_by_user?
end
+ def health_data(clusterable)
+ {
+ 'clusters-path': clusterable.index_path,
+ 'dashboard-endpoint': clusterable.metrics_dashboard_path(cluster),
+ 'documentation-path': help_page_path('user/project/clusters/index', anchor: 'monitoring-your-kubernetes-cluster-ultimate'),
+ 'add-dashboard-documentation-path': help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
+ 'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'),
+ 'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'),
+ 'empty-no-data-svg-path': image_path('illustrations/monitoring/no_data.svg'),
+ 'empty-no-data-small-svg-path': image_path('illustrations/chart-empty-state-small.svg'),
+ 'empty-unable-to-connect-svg-path': image_path('illustrations/monitoring/unable_to_connect.svg'),
+ 'settings-path': '',
+ 'project-path': '',
+ 'tags-path': ''
+ }
+ end
+
private
+ def image_path(path)
+ ActionController::Base.helpers.image_path(path)
+ end
+
+ # currently log explorer is only available in the scope of the project
+ # for group and instance level cluster selected project does not affects
+ # fetching logs from gitlab managed apps namespace, therefore any project
+ # available to user will be sufficient.
+ def logs_project
+ strong_memoize(:logs_project) do
+ cluster.all_projects.first
+ end
+ end
+
def clusterable
if cluster.group_type?
cluster.group
diff --git a/app/presenters/group_clusterable_presenter.rb b/app/presenters/group_clusterable_presenter.rb
index 21db2f6f96b..dfe8e315f94 100644
--- a/app/presenters/group_clusterable_presenter.rb
+++ b/app/presenters/group_clusterable_presenter.rb
@@ -43,6 +43,10 @@ class GroupClusterablePresenter < ClusterablePresenter
def learn_more_link
link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/group/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
+
+ def metrics_dashboard_path(cluster)
+ metrics_dashboard_group_cluster_path(clusterable, cluster)
+ end
end
GroupClusterablePresenter.prepend_if_ee('EE::GroupClusterablePresenter')
diff --git a/app/presenters/instance_clusterable_presenter.rb b/app/presenters/instance_clusterable_presenter.rb
index 41071bc7bc7..7704e6b59c1 100644
--- a/app/presenters/instance_clusterable_presenter.rb
+++ b/app/presenters/instance_clusterable_presenter.rb
@@ -81,6 +81,10 @@ class InstanceClusterablePresenter < ClusterablePresenter
def learn_more_link
link_to(s_('ClusterIntegration|Learn more about instance Kubernetes clusters'), help_page_path('user/instance/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
+
+ def metrics_dashboard_path(cluster)
+ metrics_dashboard_admin_cluster_path(cluster)
+ end
end
InstanceClusterablePresenter.prepend_if_ee('EE::InstanceClusterablePresenter')
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index af98a6ee36a..bccf0340749 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -8,6 +8,8 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
+ APPROVALS_WIDGET_BASE_TYPE = 'base'
+
presents :merge_request
def ci_status
@@ -224,6 +226,22 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
end
+ def api_approvals_path
+ expose_path(api_v4_projects_merge_requests_approvals_path(id: project.id, merge_request_iid: merge_request.iid))
+ end
+
+ def api_approve_path
+ expose_path(api_v4_projects_merge_requests_approve_path(id: project.id, merge_request_iid: merge_request.iid))
+ end
+
+ def api_unapprove_path
+ expose_path(api_v4_projects_merge_requests_unapprove_path(id: project.id, merge_request_iid: merge_request.iid))
+ end
+
+ def approvals_widget_type
+ APPROVALS_WIDGET_BASE_TYPE
+ end
+
private
def cached_can_be_reverted?
diff --git a/app/presenters/packages/composer/packages_presenter.rb b/app/presenters/packages/composer/packages_presenter.rb
new file mode 100644
index 00000000000..84f266989e9
--- /dev/null
+++ b/app/presenters/packages/composer/packages_presenter.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Packages
+ module Composer
+ class PackagesPresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ def initialize(group, packages)
+ @group = group
+ @packages = packages
+ end
+
+ def root
+ path = api_v4_group___packages_composer_package_name_path({ id: @group.id, package_name: '%package%', format: '.json' }, true)
+ { 'packages' => [], 'provider-includes' => { 'p/%hash%.json' => { 'sha256' => provider_sha } }, 'providers-url' => path }
+ end
+
+ def provider
+ { 'providers' => providers_map }
+ end
+
+ def package_versions(packages = @packages)
+ { 'packages' => { packages.first.name => package_versions_map(packages) } }
+ end
+
+ private
+
+ def package_versions_map(packages)
+ packages.each_with_object({}) do |package, map|
+ map[package.version] = package_metadata(package)
+ end
+ end
+
+ def package_metadata(package)
+ json = package.composer_metadatum.composer_json
+
+ json.merge('dist' => package_dist(package), 'uid' => package.id, 'version' => package.version)
+ end
+
+ def package_dist(package)
+ sha = package.composer_metadatum.target_sha
+ archive_api_path = api_v4_projects_packages_composer_archives_package_name_path({ id: package.project_id, package_name: package.name, format: '.zip' }, true)
+
+ {
+ 'type' => 'zip',
+ 'url' => expose_url(archive_api_path) + "?sha=#{sha}",
+ 'reference' => sha,
+ 'shasum' => ''
+ }
+ end
+
+ def providers_map
+ map = {}
+
+ @packages.group_by(&:name).each_pair do |package_name, packages|
+ map[package_name] = { 'sha256' => package_versions_sha(packages) }
+ end
+
+ map
+ end
+
+ def package_versions_sha(packages)
+ Digest::SHA256.hexdigest(package_versions(packages).to_json)
+ end
+
+ def provider_sha
+ Digest::SHA256.hexdigest(provider.to_json)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/conan/package_presenter.rb b/app/presenters/packages/conan/package_presenter.rb
new file mode 100644
index 00000000000..5141c450412
--- /dev/null
+++ b/app/presenters/packages/conan/package_presenter.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+module Packages
+ module Conan
+ class PackagePresenter
+ include API::Helpers::RelatedResourcesHelpers
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :params
+
+ def initialize(recipe, user, project, params = {})
+ @recipe = recipe
+ @user = user
+ @project = project
+ @params = params
+ end
+
+ def recipe_urls
+ map_package_files do |package_file|
+ build_recipe_file_url(package_file) if package_file.conan_file_metadatum.recipe_file?
+ end
+ end
+
+ def recipe_snapshot
+ map_package_files do |package_file|
+ package_file.file_md5 if package_file.conan_file_metadatum.recipe_file?
+ end
+ end
+
+ def package_urls
+ map_package_files do |package_file|
+ next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file)
+
+ build_package_file_url(package_file)
+ end
+ end
+
+ def package_snapshot
+ map_package_files do |package_file|
+ next unless package_file.conan_file_metadatum.package_file? && matching_reference?(package_file)
+
+ package_file.file_md5
+ end
+ end
+
+ private
+
+ def build_recipe_file_url(package_file)
+ expose_url(
+ api_v4_packages_conan_v1_files_export_path(
+ package_name: package.name,
+ package_version: package.version,
+ package_username: package.conan_metadatum.package_username,
+ package_channel: package.conan_metadatum.package_channel,
+ recipe_revision: package_file.conan_file_metadatum.recipe_revision,
+ file_name: package_file.file_name
+ )
+ )
+ end
+
+ def build_package_file_url(package_file)
+ expose_url(
+ api_v4_packages_conan_v1_files_package_path(
+ package_name: package.name,
+ package_version: package.version,
+ package_username: package.conan_metadatum.package_username,
+ package_channel: package.conan_metadatum.package_channel,
+ recipe_revision: package_file.conan_file_metadatum.recipe_revision,
+ conan_package_reference: package_file.conan_file_metadatum.conan_package_reference,
+ package_revision: package_file.conan_file_metadatum.package_revision,
+ file_name: package_file.file_name
+ )
+ )
+ end
+
+ def map_package_files
+ package_files.to_a.map do |package_file|
+ key = package_file.file_name
+ value = yield(package_file)
+ next unless key && value
+
+ [key, value]
+ end.compact.to_h
+ end
+
+ def package_files
+ return unless package
+
+ @package_files ||= package.package_files.preload_conan_file_metadata
+ end
+
+ def package
+ strong_memoize(:package) do
+ name, version = @recipe.split('@')[0].split('/')
+
+ @project.packages
+ .conan
+ .with_name(name)
+ .with_version(version)
+ .order_created
+ .last
+ end
+ end
+
+ def matching_reference?(package_file)
+ package_file.conan_file_metadatum.conan_package_reference == conan_package_reference
+ end
+
+ def conan_package_reference
+ params[:conan_package_reference]
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/detail/package_presenter.rb b/app/presenters/packages/detail/package_presenter.rb
new file mode 100644
index 00000000000..f6e068302c1
--- /dev/null
+++ b/app/presenters/packages/detail/package_presenter.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Packages
+ module Detail
+ class PackagePresenter
+ def initialize(package)
+ @package = package
+ end
+
+ def detail_view
+ package_detail = {
+ id: @package.id,
+ created_at: @package.created_at,
+ name: @package.name,
+ package_files: @package.package_files.map { |pf| build_package_file_view(pf) },
+ package_type: @package.package_type,
+ project_id: @package.project_id,
+ tags: @package.tags.as_json,
+ updated_at: @package.updated_at,
+ version: @package.version
+ }
+
+ package_detail[:maven_metadatum] = @package.maven_metadatum if @package.maven_metadatum
+ package_detail[:nuget_metadatum] = @package.nuget_metadatum if @package.nuget_metadatum
+ package_detail[:dependency_links] = @package.dependency_links.map(&method(:build_dependency_links))
+ package_detail[:pipeline] = build_pipeline_info(@package.build_info.pipeline) if @package.build_info
+
+ package_detail
+ end
+
+ private
+
+ def build_package_file_view(package_file)
+ {
+ created_at: package_file.created_at,
+ download_path: package_file.download_path,
+ file_name: package_file.file_name,
+ size: package_file.size
+ }
+ end
+
+ def build_pipeline_info(pipeline_info)
+ {
+ created_at: pipeline_info.created_at,
+ id: pipeline_info.id,
+ sha: pipeline_info.sha,
+ ref: pipeline_info.ref,
+ git_commit_message: pipeline_info.git_commit_message,
+ user: build_user_info(pipeline_info.user),
+ project: {
+ name: pipeline_info.project.name,
+ web_url: pipeline_info.project.web_url
+ }
+ }
+ end
+
+ def build_user_info(user)
+ return unless user
+
+ {
+ avatar_url: user.avatar_url,
+ name: user.name
+ }
+ end
+
+ def build_dependency_links(link)
+ {
+ name: link.dependency.name,
+ version_pattern: link.dependency.version_pattern,
+ target_framework: link.nuget_metadatum&.target_framework
+ }.compact
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/go/module_version_presenter.rb b/app/presenters/packages/go/module_version_presenter.rb
new file mode 100644
index 00000000000..4c86eae46cd
--- /dev/null
+++ b/app/presenters/packages/go/module_version_presenter.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Packages
+ module Go
+ class ModuleVersionPresenter
+ def initialize(version)
+ @version = version
+ end
+
+ def name
+ @version.name
+ end
+
+ def time
+ @version.commit.committed_date
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/npm/package_presenter.rb b/app/presenters/packages/npm/package_presenter.rb
new file mode 100644
index 00000000000..a3ab10d3913
--- /dev/null
+++ b/app/presenters/packages/npm/package_presenter.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module Packages
+ module Npm
+ class PackagePresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ attr_reader :name, :packages
+
+ NPM_VALID_DEPENDENCY_TYPES = %i[dependencies devDependencies bundleDependencies peerDependencies].freeze
+
+ def initialize(name, packages)
+ @name = name
+ @packages = packages
+ end
+
+ def versions
+ package_versions = {}
+
+ packages.each do |package|
+ package_file = package.package_files.last
+
+ next unless package_file
+
+ package_versions[package.version] = build_package_version(package, package_file)
+ end
+
+ package_versions
+ end
+
+ def dist_tags
+ build_package_tags.tap { |t| t["latest"] ||= sorted_versions.last }
+ end
+
+ private
+
+ def build_package_tags
+ Hash[
+ package_tags.map { |tag| [tag.name, tag.package.version] }
+ ]
+ end
+
+ def build_package_version(package, package_file)
+ {
+ name: package.name,
+ version: package.version,
+ dist: {
+ shasum: package_file.file_sha1,
+ tarball: tarball_url(package, package_file)
+ }
+ }.tap do |package_version|
+ package_version.merge!(build_package_dependencies(package))
+ end
+ end
+
+ def tarball_url(package, package_file)
+ expose_url "#{api_v4_projects_path(id: package.project_id)}" \
+ "/packages/npm/#{package.name}" \
+ "/-/#{package_file.file_name}"
+ end
+
+ def build_package_dependencies(package)
+ dependencies = Hash.new { |h, key| h[key] = {} }
+ dependency_links = package.dependency_links
+ .with_dependency_type(NPM_VALID_DEPENDENCY_TYPES)
+ .includes_dependency
+
+ dependency_links.find_each do |dependency_link|
+ dependency = dependency_link.dependency
+ dependencies[dependency_link.dependency_type][dependency.name] = dependency.version_pattern
+ end
+
+ dependencies
+ end
+
+ def sorted_versions
+ versions = packages.map(&:version).compact
+ VersionSorter.sort(versions)
+ end
+
+ def package_tags
+ Packages::Tag.for_packages(packages)
+ .preload_package
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/package_metadata_presenter.rb b/app/presenters/packages/nuget/package_metadata_presenter.rb
new file mode 100644
index 00000000000..500fc982e11
--- /dev/null
+++ b/app/presenters/packages/nuget/package_metadata_presenter.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class PackageMetadataPresenter
+ include Packages::Nuget::PresenterHelpers
+
+ def initialize(package)
+ @package = package
+ end
+
+ def json_url
+ json_url_for(@package)
+ end
+
+ def archive_url
+ archive_url_for(@package)
+ end
+
+ def catalog_entry
+ catalog_entry_for(@package)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/packages_metadata_presenter.rb b/app/presenters/packages/nuget/packages_metadata_presenter.rb
new file mode 100644
index 00000000000..5f22d5dd8a1
--- /dev/null
+++ b/app/presenters/packages/nuget/packages_metadata_presenter.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class PackagesMetadataPresenter
+ include Packages::Nuget::PresenterHelpers
+ include Gitlab::Utils::StrongMemoize
+
+ COUNT = 1.freeze
+
+ def initialize(packages)
+ @packages = packages
+ end
+
+ def count
+ COUNT
+ end
+
+ def items
+ [summary]
+ end
+
+ private
+
+ def summary
+ {
+ json_url: json_url,
+ lower_version: lower_version,
+ upper_version: upper_version,
+ packages_count: @packages.count,
+ packages: @packages.map { |pkg| metadata_for(pkg) }
+ }
+ end
+
+ def metadata_for(package)
+ {
+ json_url: json_url_for(package),
+ archive_url: archive_url_for(package),
+ catalog_entry: catalog_entry_for(package)
+ }
+ end
+
+ def json_url
+ json_url_for(@packages.first)
+ end
+
+ def lower_version
+ sorted_versions.first
+ end
+
+ def upper_version
+ sorted_versions.last
+ end
+
+ def sorted_versions
+ strong_memoize(:sorted_versions) do
+ versions = @packages.map(&:version).compact
+ VersionSorter.sort(versions)
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/packages_versions_presenter.rb b/app/presenters/packages/nuget/packages_versions_presenter.rb
new file mode 100644
index 00000000000..7f4ce4dbb2f
--- /dev/null
+++ b/app/presenters/packages/nuget/packages_versions_presenter.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class PackagesVersionsPresenter
+ def initialize(packages)
+ @packages = packages
+ end
+
+ def versions
+ @packages.pluck_versions.sort
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/presenter_helpers.rb b/app/presenters/packages/nuget/presenter_helpers.rb
new file mode 100644
index 00000000000..cc7e8619220
--- /dev/null
+++ b/app/presenters/packages/nuget/presenter_helpers.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ module PresenterHelpers
+ include ::API::Helpers::RelatedResourcesHelpers
+
+ BLANK_STRING = ''
+ PACKAGE_DEPENDENCY_GROUP = 'PackageDependencyGroup'
+ PACKAGE_DEPENDENCY = 'PackageDependency'
+
+ private
+
+ def json_url_for(package)
+ path = api_v4_projects_packages_nuget_metadata_package_name_package_version_path(
+ {
+ id: package.project_id,
+ package_name: package.name,
+ package_version: package.version,
+ format: '.json'
+ },
+ true
+ )
+
+ expose_url(path)
+ end
+
+ def archive_url_for(package)
+ path = api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
+ {
+ id: package.project_id,
+ package_name: package.name,
+ package_version: package.version,
+ package_filename: package.package_files.last&.file_name
+ },
+ true
+ )
+
+ expose_url(path)
+ end
+
+ def catalog_entry_for(package)
+ {
+ json_url: json_url_for(package),
+ authors: BLANK_STRING,
+ dependency_groups: dependency_groups_for(package),
+ package_name: package.name,
+ package_version: package.version,
+ archive_url: archive_url_for(package),
+ summary: BLANK_STRING,
+ tags: tags_for(package),
+ metadatum: metadatum_for(package)
+ }
+ end
+
+ def dependency_groups_for(package)
+ base_nuget_id = "#{json_url_for(package)}#dependencyGroup"
+
+ dependency_links_grouped_by_target_framework(package).map do |target_framework, dependency_links|
+ nuget_id = target_framework_nuget_id(base_nuget_id, target_framework)
+ {
+ id: nuget_id,
+ type: PACKAGE_DEPENDENCY_GROUP,
+ target_framework: target_framework,
+ dependencies: dependencies_for(nuget_id, dependency_links)
+ }.compact
+ end
+ end
+
+ def dependency_links_grouped_by_target_framework(package)
+ package
+ .dependency_links
+ .includes_dependency
+ .preload_nuget_metadatum
+ .group_by { |dependency_link| dependency_link.nuget_metadatum&.target_framework }
+ end
+
+ def dependencies_for(nuget_id, dependency_links)
+ return [] if dependency_links.empty?
+
+ dependency_links.map do |dependency_link|
+ dependency = dependency_link.dependency
+ {
+ id: "#{nuget_id}/#{dependency.name.downcase}",
+ type: PACKAGE_DEPENDENCY,
+ name: dependency.name,
+ range: dependency.version_pattern
+ }
+ end
+ end
+
+ def target_framework_nuget_id(base_nuget_id, target_framework)
+ target_framework.blank? ? base_nuget_id : "#{base_nuget_id}/#{target_framework.downcase}"
+ end
+
+ def metadatum_for(package)
+ metadatum = package.nuget_metadatum
+ return {} unless metadatum
+
+ metadatum.slice(:project_url, :license_url, :icon_url)
+ .compact
+ end
+
+ def base_path_for(package)
+ api_v4_projects_packages_nuget_path(id: package.project_id)
+ end
+
+ def tags_for(package)
+ package.tag_names.join(::Packages::Tag::NUGET_TAGS_SEPARATOR)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/search_results_presenter.rb b/app/presenters/packages/nuget/search_results_presenter.rb
new file mode 100644
index 00000000000..96c8fe7dd2a
--- /dev/null
+++ b/app/presenters/packages/nuget/search_results_presenter.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class SearchResultsPresenter
+ include Packages::Nuget::PresenterHelpers
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :total_count, to: :@search
+
+ def initialize(search)
+ @search = search
+ @package_versions = {}
+ end
+
+ def data
+ strong_memoize(:data) do
+ @search.results.group_by(&:name).map do |package_name, packages|
+ latest_version = latest_version(packages)
+ latest_package = packages.find { |pkg| pkg.version == latest_version }
+
+ {
+ type: 'Package',
+ authors: '',
+ name: package_name,
+ version: latest_version,
+ versions: build_package_versions(packages),
+ summary: '',
+ total_downloads: 0,
+ verified: true,
+ tags: tags_for(latest_package),
+ metadatum: metadatum_for(latest_package)
+ }
+ end
+ end
+ end
+
+ private
+
+ def build_package_versions(packages)
+ packages.map do |pkg|
+ {
+ json_url: json_url_for(pkg),
+ downloads: 0,
+ version: pkg.version
+ }
+ end
+ end
+
+ def latest_version(packages)
+ versions = packages.map(&:version).compact
+ VersionSorter.sort(versions).last # rubocop: disable Style/UnneededSort
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/nuget/service_index_presenter.rb b/app/presenters/packages/nuget/service_index_presenter.rb
new file mode 100644
index 00000000000..ed00b36b362
--- /dev/null
+++ b/app/presenters/packages/nuget/service_index_presenter.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class ServiceIndexPresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ SERVICE_VERSIONS = {
+ download: %w[PackageBaseAddress/3.0.0],
+ search: %w[SearchQueryService SearchQueryService/3.0.0-beta SearchQueryService/3.0.0-rc],
+ publish: %w[PackagePublish/2.0.0],
+ metadata: %w[RegistrationsBaseUrl RegistrationsBaseUrl/3.0.0-beta RegistrationsBaseUrl/3.0.0-rc]
+ }.freeze
+
+ SERVICE_COMMENTS = {
+ download: 'Get package content (.nupkg).',
+ search: 'Filter and search for packages by keyword.',
+ publish: 'Push and delete (or unlist) packages.',
+ metadata: 'Get package metadata.'
+ }.freeze
+
+ VERSION = '3.0.0'.freeze
+
+ def initialize(project)
+ @project = project
+ end
+
+ def version
+ VERSION
+ end
+
+ def resources
+ [
+ build_service(:download),
+ build_service(:search),
+ build_service(:publish),
+ build_service(:metadata)
+ ].flatten
+ end
+
+ private
+
+ def build_service(service_type)
+ url = build_service_url(service_type)
+ comment = SERVICE_COMMENTS[service_type]
+
+ SERVICE_VERSIONS[service_type].map do |version|
+ { :@id => url, :@type => version, :comment => comment }
+ end
+ end
+
+ def build_service_url(service_type)
+ base_path = api_v4_projects_packages_nuget_path(id: @project.id)
+
+ full_path = case service_type
+ when :download
+ api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
+ {
+ id: @project.id,
+ package_name: nil,
+ package_version: nil,
+ package_filename: nil
+ },
+ true
+ )
+ when :search
+ "#{base_path}/query"
+ when :metadata
+ api_v4_projects_packages_nuget_metadata_package_name_package_version_path(
+ {
+ id: @project.id,
+ package_name: nil,
+ package_version: nil
+ },
+ true
+ )
+ when :publish
+ base_path
+ end
+
+ expose_url(full_path)
+ end
+ end
+ end
+end
diff --git a/app/presenters/packages/pypi/package_presenter.rb b/app/presenters/packages/pypi/package_presenter.rb
new file mode 100644
index 00000000000..4192e974645
--- /dev/null
+++ b/app/presenters/packages/pypi/package_presenter.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+# Display package version data acording to PyPi
+# Simple API: https://warehouse.pypa.io/api-reference/legacy/#simple-project-api
+module Packages
+ module Pypi
+ class PackagePresenter
+ include API::Helpers::RelatedResourcesHelpers
+
+ def initialize(packages, project)
+ @packages = packages
+ @project = project
+ end
+
+ # Returns the HTML body for PyPi simple API.
+ # Basically a list of package download links for a specific
+ # package
+ def body
+ <<-HTML
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>Links for #{escape(name)}</title>
+ </head>
+ <body>
+ <h1>Links for #{escape(name)}</h1>
+ #{links}
+ </body>
+ </html>
+ HTML
+ end
+
+ private
+
+ def links
+ refs = []
+
+ @packages.map do |package|
+ package.package_files.each do |file|
+ url = build_pypi_package_path(file)
+
+ refs << package_link(url, package.pypi_metadatum.required_python, file.file_name)
+ end
+ end
+
+ refs.join
+ end
+
+ def package_link(url, required_python, filename)
+ "<a href=\"#{url}\" data-requires-python=\"#{escape(required_python)}\">#{filename}</a><br>"
+ end
+
+ def build_pypi_package_path(file)
+ expose_url(
+ api_v4_projects_packages_pypi_files_file_identifier_path(
+ {
+ id: @project.id,
+ sha256: file.file_sha256,
+ file_identifier: file.file_name
+ },
+ true
+ )
+ ) + "#sha256=#{file.file_sha256}"
+ end
+
+ def name
+ @packages.first.name
+ end
+
+ def escape(str)
+ ERB::Util.html_escape(str)
+ end
+ end
+ end
+end
diff --git a/app/presenters/project_clusterable_presenter.rb b/app/presenters/project_clusterable_presenter.rb
index 5c56d42ed27..718f653eab1 100644
--- a/app/presenters/project_clusterable_presenter.rb
+++ b/app/presenters/project_clusterable_presenter.rb
@@ -38,6 +38,10 @@ class ProjectClusterablePresenter < ClusterablePresenter
def learn_more_link
link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end
+
+ def metrics_dashboard_path(cluster)
+ metrics_dashboard_project_cluster_path(clusterable, cluster)
+ end
end
ProjectClusterablePresenter.prepend_if_ee('EE::ProjectClusterablePresenter')
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index a663bc555f6..4e8dae1d508 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -16,7 +16,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
MAX_TOPICS_TO_SHOW = 3
def statistic_icon(icon_name = 'plus-square-o')
- sprite_icon(icon_name, size: 16, css_class: 'icon append-right-4')
+ sprite_icon(icon_name, size: 16, css_class: 'icon gl-mr-2')
end
def statistics_anchors(show_auto_devops_callout:)
diff --git a/app/presenters/projects/prometheus/alert_presenter.rb b/app/presenters/projects/prometheus/alert_presenter.rb
index 6009ee4c7be..1cf8b202810 100644
--- a/app/presenters/projects/prometheus/alert_presenter.rb
+++ b/app/presenters/projects/prometheus/alert_presenter.rb
@@ -6,7 +6,7 @@ module Projects
RESERVED_ANNOTATIONS = %w(gitlab_incident_markdown gitlab_y_label title).freeze
GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze
MARKDOWN_LINE_BREAK = " \n".freeze
- INCIDENT_LABEL_NAME = IncidentManagement::CreateIssueService::INCIDENT_LABEL[:title].freeze
+ INCIDENT_LABEL_NAME = ::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title].freeze
METRIC_TIME_WINDOW = 30.minutes
def full_title
@@ -58,6 +58,25 @@ module Projects
MARKDOWN
end
+ def annotation_list
+ strong_memoize(:annotation_list) do
+ annotations
+ .reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) }
+ .map { |annotation| list_item(annotation.label, annotation.value) }
+ .join(MARKDOWN_LINE_BREAK)
+ end
+ end
+
+ def metric_embed_for_alert
+ "\n[](#{metrics_dashboard_url})" if metrics_dashboard_url
+ end
+
+ def metrics_dashboard_url
+ strong_memoize(:metrics_dashboard_url) do
+ embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
+ end
+ end
+
private
def alert_title
@@ -93,15 +112,6 @@ module Projects
end
end
- def annotation_list
- strong_memoize(:annotation_list) do
- annotations
- .reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) }
- .map { |annotation| list_item(annotation.label, annotation.value) }
- .join(MARKDOWN_LINE_BREAK)
- end
- end
-
def list_item(key, value)
"**#{key}:** #{value}".strip
end
@@ -120,12 +130,6 @@ module Projects
Array(hosts.value).join(' ')
end
- def metric_embed_for_alert
- url = embed_url_for_gitlab_alert || embed_url_for_self_managed_alert
-
- "\n[](#{url})" if url
- end
-
def embed_url_for_gitlab_alert
return unless gitlab_alert
@@ -133,6 +137,7 @@ module Projects
project,
gitlab_alert.prometheus_metric_id,
environment_id: environment.id,
+ embedded: true,
**alert_embed_window_params(embed_time)
)
end
@@ -144,6 +149,7 @@ module Projects
project,
environment,
embed_json: dashboard_for_self_managed_alert.to_json,
+ embedded: true,
**alert_embed_window_params(embed_time)
)
end
diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb
index 7b0a3d1e7b9..4393ca05f48 100644
--- a/app/presenters/release_presenter.rb
+++ b/app/presenters/release_presenter.rb
@@ -5,7 +5,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
presents :release
- delegate :project, :tag, :assets_count, to: :release
+ delegate :project, :tag, to: :release
def commit_path
return unless release.commit && can_download_code?
@@ -43,6 +43,18 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
edit_project_release_url(project, release)
end
+ def assets_count
+ if can_download_code?
+ release.assets_count
+ else
+ release.assets_count(except: [:sources])
+ end
+ end
+
+ def name
+ can_download_code? ? release.name : "Release-#{release.id}"
+ end
+
private
def can_download_code?
diff --git a/app/presenters/snippet_blob_presenter.rb b/app/presenters/snippet_blob_presenter.rb
index ed9c28bbc2c..d27fe751ab7 100644
--- a/app/presenters/snippet_blob_presenter.rb
+++ b/app/presenters/snippet_blob_presenter.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class SnippetBlobPresenter < BlobPresenter
+ include GitlabRoutingHelper
+
def rich_data
return if blob.binary?
return unless blob.rich_viewer
@@ -15,15 +17,17 @@ class SnippetBlobPresenter < BlobPresenter
end
def raw_path
- if snippet.is_a?(ProjectSnippet)
- raw_project_snippet_path(snippet.project, snippet)
- else
- raw_snippet_path(snippet)
- end
+ return gitlab_raw_snippet_blob_path(blob) if snippet_multiple_files?
+
+ gitlab_raw_snippet_path(snippet)
end
private
+ def snippet_multiple_files?
+ blob.container.repository_exists? && Feature.enabled?(:snippet_multiple_files, current_user)
+ end
+
def snippet
blob.container
end