diff options
Diffstat (limited to 'app')
25 files changed, 362 insertions, 118 deletions
diff --git a/app/assets/javascripts/jira_import/components/jira_import_app.vue b/app/assets/javascripts/jira_import/components/jira_import_app.vue new file mode 100644 index 00000000000..4b19c4d1b17 --- /dev/null +++ b/app/assets/javascripts/jira_import/components/jira_import_app.vue @@ -0,0 +1,28 @@ +<script> +import getJiraProjects from '../queries/getJiraProjects.query.graphql'; + +export default { + name: 'JiraImportApp', + props: { + projectPath: { + type: String, + required: true, + }, + }, + apollo: { + getJiraImports: { + query: getJiraProjects, + variables() { + return { + fullPath: this.projectPath, + }; + }, + update: data => data.project.jiraImports, + }, + }, +}; +</script> + +<template> + <div></div> +</template> diff --git a/app/assets/javascripts/jira_import/index.js b/app/assets/javascripts/jira_import/index.js new file mode 100644 index 00000000000..a17313fd774 --- /dev/null +++ b/app/assets/javascripts/jira_import/index.js @@ -0,0 +1,31 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import App from './components/jira_import_app.vue'; + +Vue.use(VueApollo); + +const defaultClient = createDefaultClient(); + +const apolloProvider = new VueApollo({ + defaultClient, +}); + +export default function mountJiraImportApp() { + const el = document.querySelector('.js-jira-import-root'); + if (!el) { + return false; + } + + return new Vue({ + el, + apolloProvider, + render(createComponent) { + return createComponent(App, { + props: { + projectPath: el.dataset.projectPath, + }, + }); + }, + }); +} diff --git a/app/assets/javascripts/jira_import/queries/getJiraProjects.query.graphql b/app/assets/javascripts/jira_import/queries/getJiraProjects.query.graphql new file mode 100644 index 00000000000..13100eac221 --- /dev/null +++ b/app/assets/javascripts/jira_import/queries/getJiraProjects.query.graphql @@ -0,0 +1,14 @@ +query getJiraProjects($fullPath: ID!) { + project(fullPath: $fullPath) { + jiraImportStatus + jiraImports { + nodes { + jiraProjectKey + scheduledAt + scheduledBy { + username + } + } + } + } +} diff --git a/app/assets/javascripts/notebook/cells/code.vue b/app/assets/javascripts/notebook/cells/code.vue index 1782e5bfe5a..f5a6f3a9817 100644 --- a/app/assets/javascripts/notebook/cells/code.vue +++ b/app/assets/javascripts/notebook/cells/code.vue @@ -21,11 +21,11 @@ export default { }, computed: { rawInputCode() { - if (this.cell.source) { + if (this.cell.source && Array.isArray(this.cell.source)) { return this.cell.source.join(''); } - return ''; + return this.cell.source || ''; }, hasOutput() { return this.cell.outputs.length; diff --git a/app/assets/javascripts/pages/projects/import/jira/index.js b/app/assets/javascripts/pages/projects/import/jira/index.js new file mode 100644 index 00000000000..cb7a7bde55d --- /dev/null +++ b/app/assets/javascripts/pages/projects/import/jira/index.js @@ -0,0 +1,3 @@ +import mountJiraImportApp from '~/jira_import'; + +document.addEventListener('DOMContentLoaded', mountJiraImportApp); diff --git a/app/assets/javascripts/pages/static_site_editor/index.js b/app/assets/javascripts/pages/projects/static_site_editor/show/index.js index 8f808dae56c..8f808dae56c 100644 --- a/app/assets/javascripts/pages/static_site_editor/index.js +++ b/app/assets/javascripts/pages/projects/static_site_editor/show/index.js diff --git a/app/assets/javascripts/projects/default_project_templates.js b/app/assets/javascripts/projects/default_project_templates.js new file mode 100644 index 00000000000..37bad1efaaf --- /dev/null +++ b/app/assets/javascripts/projects/default_project_templates.js @@ -0,0 +1,84 @@ +import { s__ } from '~/locale'; + +export default { + rails: { + text: s__('ProjectTemplates|Ruby on Rails'), + icon: '.template-option .icon-rails', + }, + express: { + text: s__('ProjectTemplates|NodeJS Express'), + icon: '.template-option .icon-express', + }, + spring: { + text: s__('ProjectTemplates|Spring'), + icon: '.template-option .icon-spring', + }, + iosswift: { + text: s__('ProjectTemplates|iOS (Swift)'), + icon: '.template-option .icon-iosswift', + }, + dotnetcore: { + text: s__('ProjectTemplates|.NET Core'), + icon: '.template-option .icon-dotnetcore', + }, + android: { + text: s__('ProjectTemplates|Android'), + icon: '.template-option .icon-android', + }, + gomicro: { + text: s__('ProjectTemplates|Go Micro'), + icon: '.template-option .icon-gomicro', + }, + gatsby: { + text: s__('ProjectTemplates|Pages/Gatsby'), + icon: '.template-option .icon-gatsby', + }, + hugo: { + text: s__('ProjectTemplates|Pages/Hugo'), + icon: '.template-option .icon-hugo', + }, + jekyll: { + text: s__('ProjectTemplates|Pages/Jekyll'), + icon: '.template-option .icon-jekyll', + }, + plainhtml: { + text: s__('ProjectTemplates|Pages/Plain HTML'), + icon: '.template-option .icon-plainhtml', + }, + gitbook: { + text: s__('ProjectTemplates|Pages/GitBook'), + icon: '.template-option .icon-gitbook', + }, + hexo: { + text: s__('ProjectTemplates|Pages/Hexo'), + icon: '.template-option .icon-hexo', + }, + nfhugo: { + text: s__('ProjectTemplates|Netlify/Hugo'), + icon: '.template-option .icon-nfhugo', + }, + nfjekyll: { + text: s__('ProjectTemplates|Netlify/Jekyll'), + icon: '.template-option .icon-nfjekyll', + }, + nfplainhtml: { + text: s__('ProjectTemplates|Netlify/Plain HTML'), + icon: '.template-option .icon-nfplainhtml', + }, + nfgitbook: { + text: s__('ProjectTemplates|Netlify/GitBook'), + icon: '.template-option .icon-nfgitbook', + }, + nfhexo: { + text: s__('ProjectTemplates|Netlify/Hexo'), + icon: '.template-option .icon-nfhexo', + }, + salesforcedx: { + text: s__('ProjectTemplates|SalesforceDX'), + icon: '.template-option .icon-salesforcedx', + }, + serverless_framework: { + text: s__('ProjectTemplates|Serverless Framework/JS'), + icon: '.template-option .icon-serverless_framework', + }, +}; diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 9cbda324aff..ebf745fd046 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils'; import { convertToTitleCase, humanize, slugify } from '../lib/utils/text_utility'; -import { s__ } from '~/locale'; +import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates'; let hasUserDefinedProjectPath = false; let hasUserDefinedProjectName = false; @@ -140,90 +140,8 @@ const bindEvents = () => { $projectFieldsForm.addClass('selected'); $selectedIcon.empty(); const value = $(this).val(); - const templates = { - rails: { - text: s__('ProjectTemplates|Ruby on Rails'), - icon: '.template-option .icon-rails', - }, - express: { - text: s__('ProjectTemplates|NodeJS Express'), - icon: '.template-option .icon-express', - }, - spring: { - text: s__('ProjectTemplates|Spring'), - icon: '.template-option .icon-spring', - }, - iosswift: { - text: s__('ProjectTemplates|iOS (Swift)'), - icon: '.template-option .icon-iosswift', - }, - dotnetcore: { - text: s__('ProjectTemplates|.NET Core'), - icon: '.template-option .icon-dotnetcore', - }, - android: { - text: s__('ProjectTemplates|Android'), - icon: '.template-option .icon-android', - }, - gomicro: { - text: s__('ProjectTemplates|Go Micro'), - icon: '.template-option .icon-gomicro', - }, - gatsby: { - text: s__('ProjectTemplates|Pages/Gatsby'), - icon: '.template-option .icon-gatsby', - }, - hugo: { - text: s__('ProjectTemplates|Pages/Hugo'), - icon: '.template-option .icon-hugo', - }, - jekyll: { - text: s__('ProjectTemplates|Pages/Jekyll'), - icon: '.template-option .icon-jekyll', - }, - plainhtml: { - text: s__('ProjectTemplates|Pages/Plain HTML'), - icon: '.template-option .icon-plainhtml', - }, - gitbook: { - text: s__('ProjectTemplates|Pages/GitBook'), - icon: '.template-option .icon-gitbook', - }, - hexo: { - text: s__('ProjectTemplates|Pages/Hexo'), - icon: '.template-option .icon-hexo', - }, - nfhugo: { - text: s__('ProjectTemplates|Netlify/Hugo'), - icon: '.template-option .icon-nfhugo', - }, - nfjekyll: { - text: s__('ProjectTemplates|Netlify/Jekyll'), - icon: '.template-option .icon-nfjekyll', - }, - nfplainhtml: { - text: s__('ProjectTemplates|Netlify/Plain HTML'), - icon: '.template-option .icon-nfplainhtml', - }, - nfgitbook: { - text: s__('ProjectTemplates|Netlify/GitBook'), - icon: '.template-option .icon-nfgitbook', - }, - nfhexo: { - text: s__('ProjectTemplates|Netlify/Hexo'), - icon: '.template-option .icon-nfhexo', - }, - salesforcedx: { - text: s__('ProjectTemplates|SalesforceDX'), - icon: '.template-option .icon-salesforcedx', - }, - serverless_framework: { - text: s__('ProjectTemplates|Serverless Framework/JS'), - icon: '.template-option .icon-serverless_framework', - }, - }; - const selectedTemplate = templates[value]; + const selectedTemplate = DEFAULT_PROJECT_TEMPLATES[value]; $selectedTemplateText.text(selectedTemplate.text); $(selectedTemplate.icon) .clone() diff --git a/app/controllers/projects/import/jira_controller.rb b/app/controllers/projects/import/jira_controller.rb index 6af630a9528..c8f53cef5b2 100644 --- a/app/controllers/projects/import/jira_controller.rb +++ b/app/controllers/projects/import/jira_controller.rb @@ -7,6 +7,8 @@ module Projects before_action :jira_integration_configured? def show + return if Feature.enabled?(:jira_issue_import_vue, @project) + unless @project.import_state&.in_progress? jira_client = @project.jira_service.client jira_projects = jira_client.Project.all diff --git a/app/controllers/projects/prometheus/metrics_controller.rb b/app/controllers/projects/prometheus/metrics_controller.rb index c9c7ba1253f..0340cb5beb0 100644 --- a/app/controllers/projects/prometheus/metrics_controller.rb +++ b/app/controllers/projects/prometheus/metrics_controller.rb @@ -20,6 +20,85 @@ module Projects end end + def validate_query + respond_to do |format| + format.json do + result = prometheus_adapter.query(:validate, params[:query]) + + if result + render json: result + else + head :accepted + end + end + end + end + + def new + @metric = project.prometheus_metrics.new + end + + def index + respond_to do |format| + format.json do + metrics = ::PrometheusMetricsFinder.new( + project: project, + ordered: true + ).execute.to_a + + response = {} + if metrics.any? + response[:metrics] = ::PrometheusMetricSerializer + .new(project: project) + .represent(metrics) + end + + render json: response + end + end + end + + def create + @metric = project.prometheus_metrics.create( + metrics_params.to_h.symbolize_keys + ) + + if @metric.persisted? + redirect_to edit_project_service_path(project, ::PrometheusService), + notice: _('Metric was successfully added.') + else + render 'new' + end + end + + def update + @metric = update_metrics_service(prometheus_metric).execute + + if @metric.persisted? + redirect_to edit_project_service_path(project, ::PrometheusService), + notice: _('Metric was successfully updated.') + else + render 'edit' + end + end + + def edit + @metric = prometheus_metric + end + + def destroy + destroy_metrics_service(prometheus_metric).execute + + respond_to do |format| + format.html do + redirect_to edit_project_service_path(project, ::PrometheusService), status: :see_other + end + format.json do + head :ok + end + end + end + private def prometheus_adapter @@ -29,8 +108,22 @@ module Projects def require_prometheus_metrics! render_404 unless prometheus_adapter&.can_query? end + + def prometheus_metric + @prometheus_metric ||= ::PrometheusMetricsFinder.new(id: params[:id]).execute.first + end + + def update_metrics_service(metric) + ::Projects::Prometheus::Metrics::UpdateService.new(metric, metrics_params) + end + + def destroy_metrics_service(metric) + ::Projects::Prometheus::Metrics::DestroyService.new(metric) + end + + def metrics_params + params.require(:prometheus_metric).permit(:title, :query, :y_label, :unit, :legend, :group) + end end end end - -Projects::Prometheus::MetricsController.prepend_if_ee('EE::Projects::Prometheus::MetricsController') diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb index 8852ae04d5e..2418ea97409 100644 --- a/app/controllers/projects/registry/repositories_controller.rb +++ b/app/controllers/projects/registry/repositories_controller.rb @@ -28,6 +28,7 @@ module Projects end def destroy + image.delete_scheduled! DeleteContainerRepositoryWorker.perform_async(current_user.id, image.id) # rubocop:disable CodeReuse/Worker track_event(:delete_repository) diff --git a/app/helpers/custom_metrics_helper.rb b/app/helpers/custom_metrics_helper.rb new file mode 100644 index 00000000000..fbea6d2050f --- /dev/null +++ b/app/helpers/custom_metrics_helper.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module CustomMetricsHelper + def custom_metrics_data(project, metric) + custom_metrics_path = project.namespace.becomes(::Namespace) + + { + 'custom-metrics-path' => url_for([custom_metrics_path, project, metric]), + 'metric-persisted' => metric.persisted?.to_s, + 'edit-project-service-path' => edit_project_service_path(project, PrometheusService), + 'validate-query-path' => validate_query_project_prometheus_metrics_path(project), + 'title' => metric.title.to_s, + 'query' => metric.query.to_s, + 'y-label' => metric.y_label.to_s, + 'unit' => metric.unit.to_s, + 'group' => metric.group.to_s, + 'legend' => metric.legend.to_s + } + end +end diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index fcf99644e66..5b640ea6538 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -18,6 +18,10 @@ module EnvironmentsHelper } end + def custom_metrics_available?(project) + can?(current_user, :admin_project, project) + end + def metrics_data(project, environment) { "settings-path" => edit_project_service_path(project, 'prometheus'), @@ -39,7 +43,10 @@ module EnvironmentsHelper "has-metrics" => "#{environment.has_metrics?}", "prometheus-status" => "#{environment.prometheus_status}", "external-dashboard-url" => project.metrics_setting_external_dashboard_url, - "environment-state" => "#{environment.state}" + "environment-state" => "#{environment.state}", + "custom-metrics-path" => project_prometheus_metrics_path(project), + "validate-query-path" => validate_query_project_prometheus_metrics_path(project), + "custom-metrics-available" => "#{custom_metrics_available?(project)}" } end diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb index b74c044b687..3bff7cb06c1 100644 --- a/app/models/container_repository.rb +++ b/app/models/container_repository.rb @@ -8,6 +8,8 @@ class ContainerRepository < ApplicationRecord validates :name, length: { minimum: 0, allow_nil: false } validates :name, uniqueness: { scope: :project_id } + enum status: { delete_scheduled: 0, delete_failed: 1 } + delegate :client, to: :registry scope :ordered, -> { order(:name) } diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index fd4ee069041..17bc29ebdcf 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -13,9 +13,9 @@ class PrometheusService < MonitoringService # to allow localhost URLs when the following conditions are true: # 1. project is the self-monitoring project. # 2. api_url is the internal Prometheus URL. - with_options presence: true, if: :manual_configuration? do - validates :api_url, public_url: true, unless: proc { |object| object.allow_local_api_url? } - validates :api_url, url: true, if: proc { |object| object.allow_local_api_url? } + with_options presence: true do + validates :api_url, public_url: true, if: ->(object) { object.manual_configuration? && !object.allow_local_api_url? } + validates :api_url, url: true, if: ->(object) { object.manual_configuration? && object.allow_local_api_url? } end before_save :synchronize_service_state diff --git a/app/serializers/container_repository_entity.rb b/app/serializers/container_repository_entity.rb index db9cf1c7835..46aa0adc5a0 100644 --- a/app/serializers/container_repository_entity.rb +++ b/app/serializers/container_repository_entity.rb @@ -3,7 +3,7 @@ class ContainerRepositoryEntity < Grape::Entity include RequestAwareEntity - expose :id, :name, :path, :location, :created_at + expose :id, :name, :path, :location, :created_at, :status expose :tags_path do |repository| project_registry_repository_tags_path(project, repository, format: :json) diff --git a/app/serializers/prometheus_metric_entity.rb b/app/serializers/prometheus_metric_entity.rb new file mode 100644 index 00000000000..ab63f3f401e --- /dev/null +++ b/app/serializers/prometheus_metric_entity.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class PrometheusMetricEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :title + + expose :group + expose :group_title + expose :unit + + expose :edit_path do |prometheus_metric| + edit_project_prometheus_metric_path(prometheus_metric.project, prometheus_metric) + end +end diff --git a/app/serializers/prometheus_metric_serializer.rb b/app/serializers/prometheus_metric_serializer.rb new file mode 100644 index 00000000000..886a6ba852f --- /dev/null +++ b/app/serializers/prometheus_metric_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class PrometheusMetricSerializer < BaseSerializer + entity PrometheusMetricEntity +end diff --git a/app/services/projects/container_repository/destroy_service.rb b/app/services/projects/container_repository/destroy_service.rb index 1f5af7970d6..83bb8624bba 100644 --- a/app/services/projects/container_repository/destroy_service.rb +++ b/app/services/projects/container_repository/destroy_service.rb @@ -8,7 +8,7 @@ module Projects # Delete tags outside of the transaction to avoid hitting an idle-in-transaction timeout container_repository.delete_tags! - container_repository.destroy + container_repository.delete_failed! unless container_repository.destroy end end end diff --git a/app/views/projects/import/jira/show.html.haml b/app/views/projects/import/jira/show.html.haml index c734297d7f3..cfc4baa1c25 100644 --- a/app/views/projects/import/jira/show.html.haml +++ b/app/views/projects/import/jira/show.html.haml @@ -1,24 +1,27 @@ -- title = _('Jira Issue Import') -- page_title title -- breadcrumb_title title -- header_title _("Projects"), root_path +- if Feature.enabled?(:jira_issue_import_vue, @project) + .js-jira-import-root{ data: { project_path: @project.full_path } } +- else + - title = _('Jira Issue Import') + - page_title title + - breadcrumb_title title + - header_title _("Projects"), root_path -= render 'import/shared/errors' + = render 'import/shared/errors' -- if @project.import_state&.in_progress? - %h3.page-title.d-flex.align-items-center - = sprite_icon('issues', size: 16, css_class: 'mr-1') - = _('Import in progress') -- elsif @jira_projects.present? - %h3.page-title.d-flex.align-items-center - = sprite_icon('issues', size: 16, css_class: 'mr-1') - = _('Import issues from Jira') + - if @project.import_state&.in_progress? + %h3.page-title.d-flex.align-items-center + = sprite_icon('issues', size: 16, css_class: 'mr-1') + = _('Import in progress') + - elsif @jira_projects.present? + %h3.page-title.d-flex.align-items-center + = sprite_icon('issues', size: 16, css_class: 'mr-1') + = _('Import issues from Jira') - = form_tag import_project_import_jira_path(@project), method: :post do - .form-group.row - = label_tag :jira_project_key, _('From project'), class: 'col-form-label col-md-2' - .col-md-4 - = select_tag :jira_project_key, options_for_select(@jira_projects, ''), { class: 'select2' } - .form-actions - = submit_tag _('Import issues'), class: 'btn btn-success' - = link_to _('Cancel'), project_issues_path(@project), class: 'btn btn-cancel' + = form_tag import_project_import_jira_path(@project), method: :post do + .form-group.row + = label_tag :jira_project_key, _('From project'), class: 'col-form-label col-md-2' + .col-md-4 + = select_tag :jira_project_key, options_for_select(@jira_projects, ''), { class: 'select2' } + .form-actions + = submit_tag _('Import issues'), class: 'btn btn-success' + = link_to _('Cancel'), project_issues_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 1bde1a41975..f7f5388a54a 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -47,9 +47,7 @@ %li.issuable-status.d-none.d-sm-inline-block = icon('ban') = _('CLOSED') - - if can?(current_user, :read_pipeline, merge_request.head_pipeline) - %li.issuable-pipeline-status.d-none.d-sm-flex - = render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user), option_css_classes: 'd-flex' + = render 'shared/merge_request_pipeline_status', merge_request: merge_request - if merge_request.open? && merge_request.broken? %li.issuable-pipeline-broken.d-none.d-sm-flex = link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do diff --git a/app/views/projects/prometheus/metrics/_form.html.haml b/app/views/projects/prometheus/metrics/_form.html.haml new file mode 100644 index 00000000000..a87d81e6325 --- /dev/null +++ b/app/views/projects/prometheus/metrics/_form.html.haml @@ -0,0 +1,4 @@ +- project = local_assigns.fetch(:project) +- metric = local_assigns.fetch(:metric) + +#js-custom-metrics{ data: custom_metrics_data(project, metric) } diff --git a/app/views/projects/prometheus/metrics/edit.html.haml b/app/views/projects/prometheus/metrics/edit.html.haml new file mode 100644 index 00000000000..15a9c922ca6 --- /dev/null +++ b/app/views/projects/prometheus/metrics/edit.html.haml @@ -0,0 +1,6 @@ +- add_to_breadcrumbs _("Settings"), edit_project_path(@project) +- add_to_breadcrumbs _("Integrations"), project_settings_integrations_path(@project) +- add_to_breadcrumbs "Prometheus", edit_project_service_path(@project, PrometheusService) +- breadcrumb_title s_('Metrics|Edit metric') +- page_title @metric.title, s_('Metrics|Edit metric') += render 'form', project: @project, metric: @metric diff --git a/app/views/projects/prometheus/metrics/new.html.haml b/app/views/projects/prometheus/metrics/new.html.haml new file mode 100644 index 00000000000..fa925d090cb --- /dev/null +++ b/app/views/projects/prometheus/metrics/new.html.haml @@ -0,0 +1,6 @@ +- add_to_breadcrumbs _("Settings"), edit_project_path(@project) +- add_to_breadcrumbs _("Integrations"), project_settings_integrations_path(@project) +- add_to_breadcrumbs "Prometheus", edit_project_service_path(@project, PrometheusService) +- breadcrumb_title s_('Metrics|New metric') +- page_title s_('Metrics|New metric') += render 'form', project: @project, metric: @metric diff --git a/app/views/shared/_merge_request_pipeline_status.html.haml b/app/views/shared/_merge_request_pipeline_status.html.haml new file mode 100644 index 00000000000..1c15b82367e --- /dev/null +++ b/app/views/shared/_merge_request_pipeline_status.html.haml @@ -0,0 +1,3 @@ +- if can?(current_user, :read_pipeline, merge_request.head_pipeline) + %li.issuable-pipeline-status.d-none.d-sm-flex + = render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user), option_css_classes: 'd-flex' |