summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/jira_import/components/jira_import_app.vue28
-rw-r--r--app/assets/javascripts/jira_import/index.js31
-rw-r--r--app/assets/javascripts/jira_import/queries/getJiraProjects.query.graphql14
-rw-r--r--app/assets/javascripts/notebook/cells/code.vue4
-rw-r--r--app/assets/javascripts/pages/projects/import/jira/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/static_site_editor/show/index.js (renamed from app/assets/javascripts/pages/static_site_editor/index.js)0
-rw-r--r--app/assets/javascripts/projects/default_project_templates.js84
-rw-r--r--app/assets/javascripts/projects/project_new.js86
-rw-r--r--app/controllers/projects/import/jira_controller.rb2
-rw-r--r--app/controllers/projects/prometheus/metrics_controller.rb97
-rw-r--r--app/controllers/projects/registry/repositories_controller.rb1
-rw-r--r--app/helpers/custom_metrics_helper.rb20
-rw-r--r--app/helpers/environments_helper.rb9
-rw-r--r--app/models/container_repository.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb6
-rw-r--r--app/serializers/container_repository_entity.rb2
-rw-r--r--app/serializers/prometheus_metric_entity.rb16
-rw-r--r--app/serializers/prometheus_metric_serializer.rb5
-rw-r--r--app/services/projects/container_repository/destroy_service.rb2
-rw-r--r--app/views/projects/import/jira/show.html.haml45
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml4
-rw-r--r--app/views/projects/prometheus/metrics/_form.html.haml4
-rw-r--r--app/views/projects/prometheus/metrics/edit.html.haml6
-rw-r--r--app/views/projects/prometheus/metrics/new.html.haml6
-rw-r--r--app/views/shared/_merge_request_pipeline_status.html.haml3
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'