summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-01-31 18:09:11 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-31 18:09:11 +0000
commit0434f38ef1dce4fe640fe1e4542235746ceb943c (patch)
tree3affe5902c9da74441dfbf5069f76c023b5cd03a
parentc27acb1d376f7127cd33eadcc8f5683ed55262bc (diff)
downloadgitlab-ce-0434f38ef1dce4fe640fe1e4542235746ceb943c.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock2
-rw-r--r--app/assets/javascripts/merge_request.js2
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/charts/index.js35
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/app.vue72
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue30
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/constants.js7
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/index.js37
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue2
-rw-r--r--app/assets/stylesheets/pages/trials.scss15
-rw-r--r--app/controllers/concerns/page_limiter.rb2
-rw-r--r--app/models/prometheus_metric.rb3
-rw-r--r--app/models/prometheus_metric_enums.rb10
-rw-r--r--app/presenters/release_presenter.rb6
-rw-r--r--app/views/projects/pipelines/charts.html.haml3
-rw-r--r--app/views/projects/pipelines/charts/_overall.haml6
-rw-r--r--app/views/projects/pipelines/charts/_pipeline_statistics.haml14
-rw-r--r--app/views/projects/pipelines/charts/_pipeline_times.haml8
-rw-r--r--app/views/projects/snippets/index.html.haml7
-rw-r--r--app/views/shared/empty_states/_profile_tabs.html.haml3
-rw-r--r--changelogs/unreleased/196183-convert-ci-cd-charts-to-echarts.yml5
-rw-r--r--changelogs/unreleased/al-55240-access-policy-check-for-new-snippet-button.yml6
-rw-r--r--changelogs/unreleased/bw-merge-request-permission.yml5
-rw-r--r--changelogs/unreleased/refactoring-entities-file-7.yml5
-rw-r--r--changelogs/unreleased/sh-fix-trigger-check-rds.yml5
-rw-r--r--config/environments/development.rb4
-rw-r--r--doc/development/code_review.md6
-rw-r--r--doc/development/documentation/styleguide.md5
-rw-r--r--doc/user/incident_management/index.md24
-rw-r--r--doc/user/project/img/labels_delete_v12_1.pngbin9531 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_list_v12_1.pngbin104907 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_new_label_from_sidebar.gifbin759243 -> 0 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_dashboard_column_panel_type.pngbin0 -> 13219 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_service_configuration.pngbin18100 -> 5022 bytes
-rw-r--r--doc/user/project/integrations/prometheus.md32
-rw-r--r--lib/api/entities.rb46
-rw-r--r--lib/api/entities/commit.rb14
-rw-r--r--lib/api/entities/commit_detail.rb22
-rw-r--r--lib/api/entities/commit_signature.rb12
-rw-r--r--lib/api/entities/commit_stats.rb9
-rw-r--r--lib/api/entities/commit_with_stats.rb9
-rw-r--r--lib/api/entities/diff_refs.rb9
-rw-r--r--lib/gitlab/alerting/notification_payload_parser.rb75
-rw-r--r--lib/gitlab/database/grant.rb11
-rw-r--r--lib/gitlab/database_importers/common_metrics.rb2
-rw-r--r--lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb9
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/controllers/concerns/page_limiter_spec.rb20
-rw-r--r--spec/controllers/explore/projects_controller_spec.rb10
-rw-r--r--spec/features/projects/graph_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_views_snippets_spec.rb97
-rw-r--r--spec/features/task_lists_spec.rb31
-rw-r--r--spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap45
-rw-r--r--spec/frontend/projects/pipelines/charts/components/app_spec.js42
-rw-r--r--spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js24
-rw-r--r--spec/frontend/projects/pipelines/charts/mock_data.js11
-rw-r--r--spec/lib/gitlab/alerting/notification_payload_parser_spec.rb134
-rw-r--r--spec/models/prometheus_metric_spec.rb3
-rw-r--r--spec/presenters/release_presenter_spec.rb16
59 files changed, 820 insertions, 203 deletions
diff --git a/Gemfile b/Gemfile
index 6ac02e92667..ac1d3d8e9c6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -332,7 +332,6 @@ group :metrics do
end
group :development do
- gem 'listen', '~> 3.0'
gem 'brakeman', '~> 4.2', require: false
gem 'danger', '~> 6.0', require: false
@@ -487,3 +486,5 @@ gem 'liquid', '~> 4.0'
# LRU cache
gem 'lru_redux'
+
+gem 'erubi', '~> 1.9.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index e61068bbf7f..ee870d6d3f5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1205,6 +1205,7 @@ DEPENDENCIES
elasticsearch-rails (~> 6.1)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
+ erubi (~> 1.9.0)
escape_utils (~> 1.1)
factory_bot_rails (~> 5.1.0)
faraday (~> 0.12)
@@ -1279,7 +1280,6 @@ DEPENDENCIES
license_finder (~> 5.4)
licensee (~> 8.9)
liquid (~> 4.0)
- listen (~> 3.0)
lograge (~> 0.5)
loofah (~> 2.2)
lru_redux
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 3a7ade5ad94..6c794c1d324 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -24,7 +24,7 @@ function MergeRequest(opts) {
this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
- if ($('a.btn-close').length) {
+ if ($('.description.js-task-list-container').length) {
this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
index 9fa580d2ba9..2e7af11c39e 100644
--- a/app/assets/javascripts/pages/projects/pipelines/charts/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
@@ -1,7 +1,9 @@
import $ from 'jquery';
import Chart from 'chart.js';
-import { barChartOptions, lineChartOptions } from '~/lib/utils/chart_utils';
+import { lineChartOptions } from '~/lib/utils/chart_utils';
+
+import initProjectPipelinesChartsApp from '~/projects/pipelines/charts/index';
const SUCCESS_LINE_COLOR = '#1aaa55';
@@ -44,40 +46,13 @@ const buildChart = (chartScope, shouldAdjustFontSize) => {
});
};
-const buildBarChart = (chartTimesData, shouldAdjustFontSize) => {
- const data = {
- labels: chartTimesData.labels,
- datasets: [
- {
- backgroundColor: 'rgba(220,220,220,0.5)',
- borderColor: 'rgba(220,220,220,1)',
- borderWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data: chartTimesData.values,
- },
- ],
- };
- return new Chart(
- $('#build_timesChart')
- .get(0)
- .getContext('2d'),
- {
- type: 'bar',
- data,
- options: barChartOptions(shouldAdjustFontSize),
- },
- );
-};
-
document.addEventListener('DOMContentLoaded', () => {
- const chartTimesData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
// Scale fonts if window width lower than 768px (iPad portrait)
const shouldAdjustFontSize = window.innerWidth < 768;
- buildBarChart(chartTimesData, shouldAdjustFontSize);
-
chartsData.forEach(scope => buildChart(scope, shouldAdjustFontSize));
});
+
+document.addEventListener('DOMContentLoaded', initProjectPipelinesChartsApp);
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
new file mode 100644
index 00000000000..4bd72c405ee
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
@@ -0,0 +1,72 @@
+<script>
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import StatisticsList from './statistics_list.vue';
+import {
+ CHART_CONTAINER_HEIGHT,
+ INNER_CHART_HEIGHT,
+ X_AXIS_LABEL_ROTATION,
+ X_AXIS_TITLE_OFFSET,
+} from '../constants';
+
+export default {
+ components: {
+ StatisticsList,
+ GlColumnChart,
+ },
+ props: {
+ counts: {
+ type: Object,
+ required: true,
+ },
+ timesChartData: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ timesChartTransformedData: {
+ full: this.mergeLabelsAndValues(this.timesChartData.labels, this.timesChartData.values),
+ },
+ };
+ },
+ methods: {
+ mergeLabelsAndValues(labels, values) {
+ return labels.map((label, index) => [label, values[index]]);
+ },
+ },
+ chartContainerHeight: CHART_CONTAINER_HEIGHT,
+ timesChartOptions: {
+ height: INNER_CHART_HEIGHT,
+ xAxis: {
+ axisLabel: {
+ rotate: X_AXIS_LABEL_ROTATION,
+ },
+ nameGap: X_AXIS_TITLE_OFFSET,
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <h4 class="my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
+ <div class="row">
+ <div class="col-md-6">
+ <statistics-list :counts="counts" />
+ </div>
+ <div class="col-md-6">
+ <strong>
+ {{ __('Duration for the last 30 commits') }}
+ </strong>
+ <gl-column-chart
+ :height="$options.chartContainerHeight"
+ :option="$options.timesChartOptions"
+ :data="timesChartTransformedData"
+ :y-axis-title="__('Minutes')"
+ :x-axis-title="__('Commit')"
+ x-axis-type="category"
+ />
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue b/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue
new file mode 100644
index 00000000000..cd9e464c5ac
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/components/statistics_list.vue
@@ -0,0 +1,30 @@
+<script>
+export default {
+ props: {
+ counts: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+<template>
+ <ul>
+ <li>
+ <span>{{ s__('PipelineCharts|Total:') }}</span>
+ <strong>{{ n__('1 pipeline', '%d pipelines', counts.total) }}</strong>
+ </li>
+ <li>
+ <span>{{ s__('PipelineCharts|Successful:') }}</span>
+ <strong>{{ n__('1 pipeline', '%d pipelines', counts.success) }}</strong>
+ </li>
+ <li>
+ <span>{{ s__('PipelineCharts|Failed:') }}</span>
+ <strong>{{ n__('1 pipeline', '%d pipelines', counts.failed) }}</strong>
+ </li>
+ <li>
+ <span>{{ s__('PipelineCharts|Success ratio:') }}</span>
+ <strong>{{ counts.successRatio }}%</strong>
+ </li>
+ </ul>
+</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/constants.js b/app/assets/javascripts/projects/pipelines/charts/constants.js
new file mode 100644
index 00000000000..eeb29370e51
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/constants.js
@@ -0,0 +1,7 @@
+export const CHART_CONTAINER_HEIGHT = 300;
+
+export const INNER_CHART_HEIGHT = 200;
+
+export const X_AXIS_LABEL_ROTATION = 45;
+
+export const X_AXIS_TITLE_OFFSET = 60;
diff --git a/app/assets/javascripts/projects/pipelines/charts/index.js b/app/assets/javascripts/projects/pipelines/charts/index.js
new file mode 100644
index 00000000000..b0f5f549980
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/index.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import ProjectPipelinesCharts from './components/app.vue';
+
+export default () => {
+ const el = document.querySelector('#js-project-pipelines-charts-app');
+ const {
+ countsFailed,
+ countsSuccess,
+ countsTotal,
+ successRatio,
+ timesChartLabels,
+ timesChartValues,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ name: 'ProjectPipelinesChartsApp',
+ components: {
+ ProjectPipelinesCharts,
+ },
+ render: createElement =>
+ createElement(ProjectPipelinesCharts, {
+ props: {
+ counts: {
+ failed: countsFailed,
+ success: countsSuccess,
+ total: countsTotal,
+ successRatio,
+ },
+ timesChartData: {
+ labels: JSON.parse(timesChartLabels),
+ values: JSON.parse(timesChartValues),
+ },
+ },
+ }),
+ });
+};
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
index cdbf57f3e55..e06149f2bcb 100644
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -74,7 +74,7 @@ export default {
</script>
<template>
- <section id="serverless-functions">
+ <section id="serverless-functions" class="flex-grow">
<gl-loading-icon
v-if="checkingInstalled"
:size="2"
diff --git a/app/assets/stylesheets/pages/trials.scss b/app/assets/stylesheets/pages/trials.scss
new file mode 100644
index 00000000000..3fb9054b2b8
--- /dev/null
+++ b/app/assets/stylesheets/pages/trials.scss
@@ -0,0 +1,15 @@
+/*
+* A CSS cross-browser fix for Select2 failire to display HTML5 required warnings
+* MR link https://gitlab.com/gitlab-org/gitlab/-/merge_requests/22716
+*/
+.gl-select2-html5-required-fix div.select2-container+select.select2 {
+ display: block !important;
+ width: 1px;
+ height: 1px;
+ z-index: -1;
+ opacity: 0;
+ margin: -3px auto 0;
+ background-image: none;
+ background-color: transparent;
+ border: 0;
+}
diff --git a/app/controllers/concerns/page_limiter.rb b/app/controllers/concerns/page_limiter.rb
index 5b078d80fca..3c280fa4f12 100644
--- a/app/controllers/concerns/page_limiter.rb
+++ b/app/controllers/concerns/page_limiter.rb
@@ -63,6 +63,6 @@ module PageLimiter
controller: params[:controller],
action: params[:action],
bot: dd.bot?
- )
+ ).increment
end
end
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index d0dc31476ff..571b586056b 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -2,6 +2,7 @@
class PrometheusMetric < ApplicationRecord
belongs_to :project, validate: true, inverse_of: :prometheus_metrics
+ has_many :prometheus_alerts, inverse_of: :prometheus_metric
enum group: PrometheusMetricEnums.groups
@@ -73,5 +74,3 @@ class PrometheusMetric < ApplicationRecord
PrometheusMetricEnums.group_details.fetch(group.to_sym)
end
end
-
-PrometheusMetric.prepend_if_ee('EE::PrometheusMetric')
diff --git a/app/models/prometheus_metric_enums.rb b/app/models/prometheus_metric_enums.rb
index cdd5e2acfce..75a34618e2c 100644
--- a/app/models/prometheus_metric_enums.rb
+++ b/app/models/prometheus_metric_enums.rb
@@ -9,7 +9,8 @@ module PrometheusMetricEnums
aws_elb: -3,
nginx: -4,
kubernetes: -5,
- nginx_ingress: -6
+ nginx_ingress: -6,
+ cluster_health: -100
}.merge(custom_groups).freeze
end
@@ -54,6 +55,11 @@ module PrometheusMetricEnums
group_title: _('System metrics (Kubernetes)'),
required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
priority: 5
+ }.freeze,
+ cluster_health: {
+ group_title: _('Cluster Health'),
+ required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
+ priority: 10
}.freeze
}.merge(custom_group_details).freeze
end
@@ -76,5 +82,3 @@ module PrometheusMetricEnums
}.freeze
end
end
-
-PrometheusMetricEnums.prepend_if_ee('EE::PrometheusMetricEnums')
diff --git a/app/presenters/release_presenter.rb b/app/presenters/release_presenter.rb
index 099ac9b09cd..2f91495c34c 100644
--- a/app/presenters/release_presenter.rb
+++ b/app/presenters/release_presenter.rb
@@ -19,6 +19,12 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
project_tag_path(project, release.tag)
end
+ def self_url
+ return unless ::Feature.enabled?(:release_show_page, project)
+
+ project_release_url(project, release)
+ end
+
def merge_requests_url
return unless release_mr_issue_urls_available?
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index c9a50b97fea..9542f8c9766 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,6 +1,7 @@
- page_title _('CI / CD Charts')
+#js-project-pipelines-charts-app{ data: { counts: @counts, success_ratio: success_ratio(@counts), times_chart: { labels: @charts[:pipeline_times].labels, values: @charts[:pipeline_times].pipeline_times } } }
+
#charts.ci-charts
- = render 'projects/pipelines/charts/overall'
%hr
= render 'projects/pipelines/charts/pipelines'
diff --git a/app/views/projects/pipelines/charts/_overall.haml b/app/views/projects/pipelines/charts/_overall.haml
deleted file mode 100644
index 651f9217455..00000000000
--- a/app/views/projects/pipelines/charts/_overall.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-%h4.mt-4.mb-4= s_("PipelineCharts|Overall statistics")
-.row
- .col-md-6
- = render 'projects/pipelines/charts/pipeline_statistics'
- .col-md-6
- = render 'projects/pipelines/charts/pipeline_times'
diff --git a/app/views/projects/pipelines/charts/_pipeline_statistics.haml b/app/views/projects/pipelines/charts/_pipeline_statistics.haml
deleted file mode 100644
index b323e290ed4..00000000000
--- a/app/views/projects/pipelines/charts/_pipeline_statistics.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%ul
- %li
- = s_("PipelineCharts|Total:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
- %li
- = s_("PipelineCharts|Successful:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
- %li
- = s_("PipelineCharts|Failed:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
- %li
- = s_("PipelineCharts|Success ratio:")
- %strong
- #{success_ratio(@counts)}%
diff --git a/app/views/projects/pipelines/charts/_pipeline_times.haml b/app/views/projects/pipelines/charts/_pipeline_times.haml
deleted file mode 100644
index c0ac79ed5f8..00000000000
--- a/app/views/projects/pipelines/charts/_pipeline_times.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%p.light
- = _("Commit duration in minutes for last 30 commits")
-
-%div
- %canvas#build_timesChart{ height: 200 }
-
--# haml-lint:disable InlineJavaScript
-%script#pipelinesTimesChartsData{ type: "application/json" }= { :labels => @charts[:pipeline_times].labels, :values => @charts[:pipeline_times].pipeline_times }.to_json.html_safe
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 65462647419..ed56cc8289c 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,4 +1,5 @@
- page_title _("Snippets")
+- new_project_snippet_link = new_project_snippet_path(@project) if can?(current_user, :create_snippet, @project)
- if @snippets.exists?
- if current_user
@@ -6,10 +7,10 @@
- include_private = @project.team.member?(current_user) || current_user.admin?
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
- - if can?(current_user, :create_snippet, @project)
+ - if new_project_snippet_link.present?
.nav-controls
- = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet")
+ = link_to _("New snippet"), new_project_snippet_link, class: "btn btn-success", title: _("New snippet")
= render 'shared/snippets/list'
- else
- = render 'shared/empty_states/snippets', button_path: new_namespace_project_snippet_path(@project.namespace, @project)
+ = render 'shared/empty_states/snippets', button_path: new_project_snippet_link
diff --git a/app/views/shared/empty_states/_profile_tabs.html.haml b/app/views/shared/empty_states/_profile_tabs.html.haml
index 98a5a5953d0..38c9fe7179c 100644
--- a/app/views/shared/empty_states/_profile_tabs.html.haml
+++ b/app/views/shared/empty_states/_profile_tabs.html.haml
@@ -14,6 +14,7 @@
- if secondary_button_link.present?
= link_to secondary_button_label, secondary_button_link, class: 'btn btn-success btn-inverted'
- = link_to primary_button_label, primary_button_link, class: 'btn btn-success'
+ - if primary_button_link.present?
+ = link_to primary_button_label, primary_button_link, class: 'btn btn-success'
- else
%h5= visitor_empty_message
diff --git a/changelogs/unreleased/196183-convert-ci-cd-charts-to-echarts.yml b/changelogs/unreleased/196183-convert-ci-cd-charts-to-echarts.yml
new file mode 100644
index 00000000000..afa99aae792
--- /dev/null
+++ b/changelogs/unreleased/196183-convert-ci-cd-charts-to-echarts.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate CI CD statistics + duration chart to VueJS
+merge_request: 23840
+author:
+type: changed
diff --git a/changelogs/unreleased/al-55240-access-policy-check-for-new-snippet-button.yml b/changelogs/unreleased/al-55240-access-policy-check-for-new-snippet-button.yml
new file mode 100644
index 00000000000..b4774045350
--- /dev/null
+++ b/changelogs/unreleased/al-55240-access-policy-check-for-new-snippet-button.yml
@@ -0,0 +1,6 @@
+---
+title: Ensure New Snippet button is displayed based on the :create_snippet permission
+ in Project Snippets page and User profile > Snippets tab
+merge_request: 55240
+author:
+type: fixed
diff --git a/changelogs/unreleased/bw-merge-request-permission.yml b/changelogs/unreleased/bw-merge-request-permission.yml
new file mode 100644
index 00000000000..620e1a97401
--- /dev/null
+++ b/changelogs/unreleased/bw-merge-request-permission.yml
@@ -0,0 +1,5 @@
+---
+title: Task lists work correctly again on closed MRs
+merge_request: 23714
+author:
+type: fixed
diff --git a/changelogs/unreleased/refactoring-entities-file-7.yml b/changelogs/unreleased/refactoring-entities-file-7.yml
new file mode 100644
index 00000000000..d73864c9dc0
--- /dev/null
+++ b/changelogs/unreleased/refactoring-entities-file-7.yml
@@ -0,0 +1,5 @@
+---
+title: Separate commit entities into own class files
+merge_request: 24085
+author: Rajendra Kadam
+type: added
diff --git a/changelogs/unreleased/sh-fix-trigger-check-rds.yml b/changelogs/unreleased/sh-fix-trigger-check-rds.yml
new file mode 100644
index 00000000000..99ae6039aaa
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-trigger-check-rds.yml
@@ -0,0 +1,5 @@
+---
+title: Fix database permission check for triggers on Amazon RDS
+merge_request: 24035
+author:
+type: fixed
diff --git a/config/environments/development.rb b/config/environments/development.rb
index 960892a1dc2..dc804197fef 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -50,8 +50,4 @@ Rails.application.configure do
# BetterErrors live shell (REPL) on every stack frame
BetterErrors::Middleware.allow_ip!("127.0.0.1/0")
-
- # Use an evented file watcher to asynchronously detect changes in source code,
- # routes, locales, etc. This feature depends on the listen gem.
- config.file_watcher = ActiveSupport::EventedFileUpdateChecker
end
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 4bc963222f2..9b82cf5ff99 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -59,12 +59,12 @@ are recommended to get your merge request approved and merged by maintainer(s)
from teams other than your own.
1. If your merge request includes backend changes [^1], it must be
- **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**.
+ **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_backend)**.
1. If your merge request includes database migrations or changes to expensive queries [^2], it must be
- **approved by a [database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_database)**.
+ **approved by a [database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_database)**.
Read the [database review guidelines](database_review.md) for more details.
1. If your merge request includes frontend changes [^1], it must be
- **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**.
+ **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_frontend)**.
1. If your merge request includes UX changes [^1], it must be
**approved by a [UX team member](https://about.gitlab.com/company/team/)**.
1. If your merge request includes adding a new JavaScript library [^1], it must be
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 9c4b97c4adf..f979f882948 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -645,9 +645,8 @@ To indicate the steps of navigation through the UI:
- Images should have a specific, non-generic name that will
differentiate and describe them properly.
- Always add to the end of the file name the GitLab release version
- number corresponding to the release milestone the image was added to,
- or corresponding to the release the screenshot was taken from, using the
- format `image_name_vX_Y.png`.
+ corresponding to the version the screenshot was taken from, using the format
+ `image_name_vX_Y.png`.
([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/issues/61027) in GitLab 12.1.)
- For example, for a screenshot taken from the pipelines page of
GitLab 11.1, a valid name is `pipelines_v11_1.png`. If you're
diff --git a/doc/user/incident_management/index.md b/doc/user/incident_management/index.md
index febe1a2423a..c644c5801df 100644
--- a/doc/user/incident_management/index.md
+++ b/doc/user/incident_management/index.md
@@ -98,30 +98,6 @@ members can join swiftly without requesting a link.
Read more how to [add or remove a zoom meeting](../project/issues/associate_zoom_meeting.md).
-### Alerting
-
-You can let GitLab know of alerts that may be triggering in your applications and services. GitLab can react to these by automatically creating Issues, and alerting developers via Email.
-
-#### Prometheus Alerts
-
-Prometheus alerts can be setup in both GitLab-managed Prometheus installs and self-managed Prometheus installs.
-
-Documentation for each method can be found here:
-
-- [GitLab-managed Prometheus](../project/integrations/prometheus.md#setting-up-alerts-for-prometheus-metrics-ultimate)
-- [Self-managed Prometheus](../project/integrations/prometheus.md#external-prometheus-instances)
-
-#### Alert Endpoint
-
-GitLab can accept alerts from any source via a generic webhook receiver. When you set up the generic alerts integration, a unique endpoint will
-be created which can receive a payload in JSON format.
-
-More information on setting this up, including how to customize the payload [can be found here](../project/integrations/generic_alerts.md).
-
-#### Recovery Alerts
-
-Coming soon: GitLab can automatically close Issues that have been automatically created when we receive notification that the alert is resolved.
-
### Configuring Incidents
Incident Management features can be easily enabled & disabled via the Project settings page. Head to Project -> Settings -> Operations -> Incidents.
diff --git a/doc/user/project/img/labels_delete_v12_1.png b/doc/user/project/img/labels_delete_v12_1.png
deleted file mode 100644
index 566e0519fbe..00000000000
--- a/doc/user/project/img/labels_delete_v12_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_list_v12_1.png b/doc/user/project/img/labels_list_v12_1.png
deleted file mode 100644
index 47359d05f7f..00000000000
--- a/doc/user/project/img/labels_list_v12_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_new_label_from_sidebar.gif b/doc/user/project/img/labels_new_label_from_sidebar.gif
deleted file mode 100644
index 572b29a86e1..00000000000
--- a/doc/user/project/img/labels_new_label_from_sidebar.gif
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_dashboard_column_panel_type.png b/doc/user/project/integrations/img/prometheus_dashboard_column_panel_type.png
new file mode 100644
index 00000000000..985f2b04ef3
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_dashboard_column_panel_type.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_service_configuration.png b/doc/user/project/integrations/img/prometheus_service_configuration.png
index c7dfe874817..a38d1bce197 100644
--- a/doc/user/project/integrations/img/prometheus_service_configuration.png
+++ b/doc/user/project/integrations/img/prometheus_service_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index d8938d81637..90026833ec6 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -96,9 +96,8 @@ to integrate with.
1. Navigate to the [Integrations page](project_services.md#accessing-the-project-services)
1. Click the **Prometheus** service
-1. Provide the base URL of the your server, for example `http://prometheus.example.com/`.
- The **Test Settings** button can be used to confirm connectivity from GitLab
- to the Prometheus server.
+1. Provide the base URL of your server, for example `http://prometheus.example.com/`
+1. Click **Save changes**
![Configure Prometheus Service](img/prometheus_service_configuration.png)
@@ -330,6 +329,33 @@ Note the following properties:
![anomaly panel type](img/prometheus_dashboard_anomaly_panel_type.png)
+#### Column
+
+To add a column panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group title'
+ panels:
+ - title: "Column"
+ type: "column"
+ metrics:
+ - id: 1024_memory
+ query: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
+ unit: MB
+ label: "Memory Usage"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | yes | Type of panel to be rendered. For column panel types, set to `column` |
+| query_range | yes | yes | For column panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
+
+![anomaly panel type](img/prometheus_dashboard_column_panel_type.png)
+
##### Single Stat
To add a single stat panel type to a dashboard, look at the following sample dashboard file:
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 681bb98b155..451d7c9edca 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -128,51 +128,6 @@ module API
end
end
- class DiffRefs < Grape::Entity
- expose :base_sha, :head_sha, :start_sha
- end
-
- class Commit < Grape::Entity
- expose :id, :short_id, :created_at
- expose :parent_ids
- expose :full_title, as: :title
- expose :safe_message, as: :message
- expose :author_name, :author_email, :authored_date
- expose :committer_name, :committer_email, :committed_date
- end
-
- class CommitStats < Grape::Entity
- expose :additions, :deletions, :total
- end
-
- class CommitWithStats < Commit
- expose :stats, using: Entities::CommitStats
- end
-
- class CommitDetail < Commit
- expose :stats, using: Entities::CommitStats, if: :stats
- expose :status
- expose :project_id
-
- expose :last_pipeline do |commit, options|
- pipeline = commit.last_pipeline if can_read_pipeline?
- ::API::Entities::PipelineBasic.represent(pipeline, options)
- end
-
- private
-
- def can_read_pipeline?
- Ability.allowed?(options[:current_user], :read_pipeline, object.last_pipeline)
- end
- end
-
- class CommitSignature < Grape::Entity
- expose :gpg_key_id
- expose :gpg_key_primary_keyid, :gpg_key_user_name, :gpg_key_user_email
- expose :verification_status
- expose :gpg_key_subkey_id
- end
-
class BasicRef < Grape::Entity
expose :type, :name
end
@@ -1101,6 +1056,7 @@ module API
expose :evidence_file_path, expose_nil: false, if: ->(_, _) { can_download_code? }
end
expose :_links do
+ expose :self_url, as: :self, expose_nil: false
expose :merge_requests_url, expose_nil: false
expose :issues_url, expose_nil: false
expose :edit_url, expose_nil: false
diff --git a/lib/api/entities/commit.rb b/lib/api/entities/commit.rb
new file mode 100644
index 00000000000..7ce97c2c3d8
--- /dev/null
+++ b/lib/api/entities/commit.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class Commit < Grape::Entity
+ expose :id, :short_id, :created_at
+ expose :parent_ids
+ expose :full_title, as: :title
+ expose :safe_message, as: :message
+ expose :author_name, :author_email, :authored_date
+ expose :committer_name, :committer_email, :committed_date
+ end
+ end
+end
diff --git a/lib/api/entities/commit_detail.rb b/lib/api/entities/commit_detail.rb
new file mode 100644
index 00000000000..22424b38bb9
--- /dev/null
+++ b/lib/api/entities/commit_detail.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class CommitDetail < Commit
+ expose :stats, using: Entities::CommitStats, if: :stats
+ expose :status
+ expose :project_id
+
+ expose :last_pipeline do |commit, options|
+ pipeline = commit.last_pipeline if can_read_pipeline?
+ ::API::Entities::PipelineBasic.represent(pipeline, options)
+ end
+
+ private
+
+ def can_read_pipeline?
+ Ability.allowed?(options[:current_user], :read_pipeline, object.last_pipeline)
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/commit_signature.rb b/lib/api/entities/commit_signature.rb
new file mode 100644
index 00000000000..8e86d4c1aa6
--- /dev/null
+++ b/lib/api/entities/commit_signature.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class CommitSignature < Grape::Entity
+ expose :gpg_key_id
+ expose :gpg_key_primary_keyid, :gpg_key_user_name, :gpg_key_user_email
+ expose :verification_status
+ expose :gpg_key_subkey_id
+ end
+ end
+end
diff --git a/lib/api/entities/commit_stats.rb b/lib/api/entities/commit_stats.rb
new file mode 100644
index 00000000000..d9ba99c8eb0
--- /dev/null
+++ b/lib/api/entities/commit_stats.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class CommitStats < Grape::Entity
+ expose :additions, :deletions, :total
+ end
+ end
+end
diff --git a/lib/api/entities/commit_with_stats.rb b/lib/api/entities/commit_with_stats.rb
new file mode 100644
index 00000000000..8a992586e22
--- /dev/null
+++ b/lib/api/entities/commit_with_stats.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class CommitWithStats < Commit
+ expose :stats, using: Entities::CommitStats
+ end
+ end
+end
diff --git a/lib/api/entities/diff_refs.rb b/lib/api/entities/diff_refs.rb
new file mode 100644
index 00000000000..8772fa2334f
--- /dev/null
+++ b/lib/api/entities/diff_refs.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class DiffRefs < Grape::Entity
+ expose :base_sha, :head_sha, :start_sha
+ end
+ end
+end
diff --git a/lib/gitlab/alerting/notification_payload_parser.rb b/lib/gitlab/alerting/notification_payload_parser.rb
new file mode 100644
index 00000000000..a54bb44d66a
--- /dev/null
+++ b/lib/gitlab/alerting/notification_payload_parser.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Alerting
+ class NotificationPayloadParser
+ BadPayloadError = Class.new(StandardError)
+
+ DEFAULT_TITLE = 'New: Incident'
+
+ def initialize(payload)
+ @payload = payload.to_h.with_indifferent_access
+ end
+
+ def self.call(payload)
+ new(payload).call
+ end
+
+ def call
+ {
+ 'annotations' => annotations,
+ 'startsAt' => starts_at
+ }.compact
+ end
+
+ private
+
+ attr_reader :payload
+
+ def title
+ payload[:title].presence || DEFAULT_TITLE
+ end
+
+ def annotations
+ primary_params
+ .reverse_merge(flatten_secondary_params)
+ .transform_values(&:presence)
+ .compact
+ end
+
+ def primary_params
+ {
+ 'title' => title,
+ 'description' => payload[:description],
+ 'monitoring_tool' => payload[:monitoring_tool],
+ 'service' => payload[:service],
+ 'hosts' => hosts.presence
+ }
+ end
+
+ def hosts
+ Array(payload[:hosts]).reject(&:blank?)
+ end
+
+ def current_time
+ Time.current.change(usec: 0).rfc3339
+ end
+
+ def starts_at
+ Time.parse(payload[:start_time].to_s).rfc3339
+ rescue ArgumentError
+ current_time
+ end
+
+ def secondary_params
+ payload.except(:start_time)
+ end
+
+ def flatten_secondary_params
+ Gitlab::Utils::SafeInlineHash.merge_keys!(secondary_params)
+ rescue ArgumentError
+ raise BadPayloadError, 'The payload is too big'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/grant.rb b/lib/gitlab/database/grant.rb
index 1f47f320a29..7774dd9fffe 100644
--- a/lib/gitlab/database/grant.rb
+++ b/lib/gitlab/database/grant.rb
@@ -3,23 +3,18 @@
module Gitlab
module Database
# Model that can be used for querying permissions of a SQL user.
- class Grant < ActiveRecord::Base
- include FromUnion
-
- self.table_name = 'information_schema.role_table_grants'
-
+ class Grant
# Returns true if the current user can create and execute triggers on the
# given table.
def self.create_and_execute_trigger?(table)
# We _must not_ use quote_table_name as this will produce double
# quotes on PostgreSQL and for "has_table_privilege" we need single
# quotes.
+ connection = ActiveRecord::Base.connection
quoted_table = connection.quote(table)
begin
- from(nil)
- .pluck(Arel.sql("has_table_privilege(#{quoted_table}, 'TRIGGER')"))
- .first
+ connection.select_one("SELECT has_table_privilege(#{quoted_table}, 'TRIGGER')").present?
rescue ActiveRecord::StatementInvalid
# This error is raised when using a non-existing table name. In this
# case we just want to return false as a user technically can't
diff --git a/lib/gitlab/database_importers/common_metrics.rb b/lib/gitlab/database_importers/common_metrics.rb
index b9d320f2fc7..f964ae8a275 100644
--- a/lib/gitlab/database_importers/common_metrics.rb
+++ b/lib/gitlab/database_importers/common_metrics.rb
@@ -6,5 +6,3 @@ module Gitlab
end
end
end
-
-Gitlab::DatabaseImporters::CommonMetrics.prepend_if_ee('EE::Gitlab::DatabaseImporters::CommonMetrics')
diff --git a/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb b/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb
index 409a1252da1..fb0fcc5a93b 100644
--- a/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb
+++ b/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb
@@ -17,7 +17,9 @@ module Gitlab
# custom groups
business: 0,
response: 1,
- system: 2
+ system: 2,
+
+ cluster_health: -100
}
end
@@ -31,12 +33,11 @@ module Gitlab
ha_proxy: _('Response metrics (HA Proxy)'),
aws_elb: _('Response metrics (AWS ELB)'),
nginx: _('Response metrics (NGINX)'),
- kubernetes: _('System metrics (Kubernetes)')
+ kubernetes: _('System metrics (Kubernetes)'),
+ cluster_health: _('Cluster Health')
}
end
end
end
end
end
-
-::Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetricEnums.prepend_if_ee('EE::Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetricEnums')
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 71043671be7..1c1fc8d16ff 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -4801,9 +4801,6 @@ msgstr ""
msgid "Commit deleted"
msgstr ""
-msgid "Commit duration in minutes for last 30 commits"
-msgstr ""
-
msgid "Commit message"
msgstr ""
@@ -6813,6 +6810,9 @@ msgstr ""
msgid "Duration"
msgstr ""
+msgid "Duration for the last 30 commits"
+msgstr ""
+
msgid "During this process, you’ll be asked for URLs from GitLab’s side. Use the URLs shown below."
msgstr ""
diff --git a/spec/controllers/concerns/page_limiter_spec.rb b/spec/controllers/concerns/page_limiter_spec.rb
index 9ac94b7e740..287b62cb66c 100644
--- a/spec/controllers/concerns/page_limiter_spec.rb
+++ b/spec/controllers/concerns/page_limiter_spec.rb
@@ -71,19 +71,23 @@ describe PageLimiter do
describe "#default_page_out_of_bounds_response" do
subject { instance.send(:default_page_out_of_bounds_response) }
- after do
- subject
- end
-
it "returns a bad_request header" do
expect(instance).to receive(:head).with(:bad_request)
+
+ subject
end
end
describe "#record_page_limit_interception" do
subject { instance.send(:record_page_limit_interception) }
- it "records a metric counter" do
+ let(:counter) { double("counter", increment: true) }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter) { counter }
+ end
+
+ it "creates a metric counter" do
expect(Gitlab::Metrics).to receive(:counter).with(
:gitlab_page_out_of_bounds,
controller: "explore/projects",
@@ -93,5 +97,11 @@ describe PageLimiter do
subject
end
+
+ it "increments the counter" do
+ expect(counter).to receive(:increment)
+
+ subject
+ end
end
end
diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb
index 6f68de52845..c2cd29eb036 100644
--- a/spec/controllers/explore/projects_controller_spec.rb
+++ b/spec/controllers/explore/projects_controller_spec.rb
@@ -90,8 +90,12 @@ describe Explore::ProjectsController do
end
describe "metrics recording" do
- after do
- get endpoint, params: { page: page_limit + 1 }
+ subject { get endpoint, params: { page: page_limit + 1 } }
+
+ let(:counter) { double("counter", increment: true) }
+
+ before do
+ allow(Gitlab::Metrics).to receive(:counter) { counter }
end
it "records the interception" do
@@ -101,6 +105,8 @@ describe Explore::ProjectsController do
action: endpoint.to_s,
bot: false
)
+
+ subject
end
end
end
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
index 5dabaf20952..7b5c731c34b 100644
--- a/spec/features/projects/graph_spec.rb
+++ b/spec/features/projects/graph_spec.rb
@@ -85,7 +85,7 @@ describe 'Project Graph', :js do
expect(page).to have_content 'Pipelines for last week'
expect(page).to have_content 'Pipelines for last month'
expect(page).to have_content 'Pipelines for last year'
- expect(page).to have_content 'Commit duration in minutes for last 30 commits'
+ expect(page).to have_content 'Duration for the last 30 commits'
end
end
end
diff --git a/spec/features/projects/snippets/user_views_snippets_spec.rb b/spec/features/projects/snippets/user_views_snippets_spec.rb
index 5739c9510a8..a02f7f8a8d2 100644
--- a/spec/features/projects/snippets/user_views_snippets_spec.rb
+++ b/spec/features/projects/snippets/user_views_snippets_spec.rb
@@ -3,32 +3,97 @@
require 'spec_helper'
describe 'Projects > Snippets > User views snippets' do
- let(:project) { create(:project) }
- let!(:project_snippet) { create(:project_snippet, project: project, author: user) }
- let!(:snippet) { create(:snippet, author: user) }
- let(:snippets) { [project_snippet, snippet] } # Used by the shared examples
+ let_it_be(:project) { create(:project) }
let(:user) { create(:user) }
- before do
- project.add_maintainer(user)
- sign_in(user)
-
+ def visit_project_snippets
visit(project_snippets_path(project))
end
- context 'pagination' do
+ context 'snippets list' do
+ let!(:project_snippet) { create(:project_snippet, project: project, author: user) }
+ let!(:snippet) { create(:snippet, author: user) }
+ let(:snippets) { [project_snippet, snippet] } # Used by the shared examples
+
before do
- create(:project_snippet, project: project, author: user)
- allow(Snippet).to receive(:default_per_page).and_return(1)
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context 'pagination' do
+ before do
+ create(:project_snippet, project: project, author: user)
+ allow(Snippet).to receive(:default_per_page).and_return(1)
- visit project_snippets_path(project)
+ visit_project_snippets
+ end
+
+ it_behaves_like 'paginated snippets'
end
- it_behaves_like 'paginated snippets'
+ it 'shows snippets' do
+ visit_project_snippets
+
+ expect(page).to have_link(project_snippet.title, href: project_snippet_path(project, project_snippet))
+ expect(page).not_to have_content(snippet.title)
+ end
end
- it 'shows snippets' do
- expect(page).to have_link(project_snippet.title, href: project_snippet_path(project, project_snippet))
- expect(page).not_to have_content(snippet.title)
+ context 'when current user is a guest' do
+ before do
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ context 'when snippets list is empty' do
+ it 'hides New Snippet button' do
+ visit_project_snippets
+
+ page.within(find('.empty-state')) do
+ expect(page).not_to have_link('New snippet')
+ end
+ end
+ end
+
+ context 'when project has snippets' do
+ let!(:project_snippet) { create(:project_snippet, project: project, author: user) }
+
+ it 'hides New Snippet button' do
+ visit_project_snippets
+
+ page.within(find('.top-area')) do
+ expect(page).not_to have_link('New snippet')
+ end
+ end
+ end
+ end
+
+ context 'when current user is not a guest' do
+ before do
+ project.add_developer(user)
+ sign_in(user)
+ end
+
+ context 'when snippets list is empty' do
+ it 'shows New Snippet button' do
+ visit_project_snippets
+
+ page.within(find('.empty-state')) do
+ expect(page).to have_link('New snippet')
+ end
+ end
+ end
+
+ context 'when project has snippets' do
+ let!(:project_snippet) { create(:project_snippet, project: project, author: user) }
+
+ it 'shows New Snippet button' do
+ visit_project_snippets
+
+ page.within(find('.top-area')) do
+ expect(page).to have_link('New snippet')
+ end
+ end
+ end
end
end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index bcd894a0d20..0d10f6aee3b 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe 'Task Lists' do
include Warden::Test::Helpers
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :public, :repository) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
@@ -122,6 +122,7 @@ describe 'Task Lists' do
it 'provides a summary on Issues#index' do
visit project_issues_path(project)
+
expect(page).to have_content("2 of 6 tasks completed")
end
end
@@ -191,6 +192,7 @@ describe 'Task Lists' do
it 'is only editable by author', :js do
visit_issue(project, issue)
+
expect(page).to have_selector('.js-task-list-container')
gitlab_sign_out
@@ -237,10 +239,7 @@ describe 'Task Lists' do
visit project_merge_request_path(project, merge)
end
- describe 'multiple tasks' do
- let(:project) { create(:project, :repository) }
- let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
-
+ shared_examples 'multiple tasks' do
it 'renders for description' do
visit_merge_request(project, merge)
@@ -261,23 +260,40 @@ describe 'Task Lists' do
expect(page).to have_selector('a.btn-close')
end
- it 'is only editable by author' do
+ it 'is only editable by author', :js do
visit_merge_request(project, merge)
+
expect(page).to have_selector('.js-task-list-container')
+ expect(page).to have_selector('li.task-list-item.enabled', count: 6)
logout(:user)
-
login_as(user2)
visit current_path
+
expect(page).not_to have_selector('.js-task-list-container')
+ expect(page).to have_selector('li.task-list-item.enabled', count: 0)
+ expect(page).to have_selector('li.task-list-item input[disabled]', count: 6)
end
+ end
+
+ context 'when merge request is open' do
+ let!(:merge) { create(:merge_request, :simple, description: markdown, author: user, source_project: project) }
+
+ it_behaves_like 'multiple tasks'
it 'provides a summary on MergeRequests#index' do
visit project_merge_requests_path(project)
+
expect(page).to have_content("2 of 6 tasks completed")
end
end
+ context 'when merge request is closed' do
+ let!(:merge) { create(:merge_request, :closed, :simple, description: markdown, author: user, source_project: project) }
+
+ it_behaves_like 'multiple tasks'
+ end
+
describe 'single incomplete task' do
let!(:merge) { create(:merge_request, :simple, description: singleIncompleteMarkdown, author: user, source_project: project) }
@@ -291,6 +307,7 @@ describe 'Task Lists' do
it 'provides a summary on MergeRequests#index' do
visit project_merge_requests_path(project)
+
expect(page).to have_content("0 of 1 task completed")
end
end
diff --git a/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap b/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap
new file mode 100644
index 00000000000..ff0351bd099
--- /dev/null
+++ b/spec/frontend/projects/pipelines/charts/components/__snapshots__/statistics_list_spec.js.snap
@@ -0,0 +1,45 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`StatisticsList matches the snapshot 1`] = `
+<ul>
+ <li>
+ <span>
+ Total:
+ </span>
+
+ <strong>
+ 4 pipelines
+ </strong>
+ </li>
+
+ <li>
+ <span>
+ Successful:
+ </span>
+
+ <strong>
+ 2 pipelines
+ </strong>
+ </li>
+
+ <li>
+ <span>
+ Failed:
+ </span>
+
+ <strong>
+ 2 pipelines
+ </strong>
+ </li>
+
+ <li>
+ <span>
+ Success ratio:
+ </span>
+
+ <strong>
+ 50%
+ </strong>
+ </li>
+</ul>
+`;
diff --git a/spec/frontend/projects/pipelines/charts/components/app_spec.js b/spec/frontend/projects/pipelines/charts/components/app_spec.js
new file mode 100644
index 00000000000..48ea333e2c3
--- /dev/null
+++ b/spec/frontend/projects/pipelines/charts/components/app_spec.js
@@ -0,0 +1,42 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlColumnChart } from '@gitlab/ui/dist/charts';
+import Component from '~/projects/pipelines/charts/components/app';
+import StatisticsList from '~/projects/pipelines/charts/components/statistics_list';
+import { counts, timesChartData } from '../mock_data';
+
+describe('ProjectsPipelinesChartsApp', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(Component, {
+ propsData: {
+ counts,
+ timesChartData,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('overall statistics', () => {
+ it('displays the statistics list', () => {
+ const list = wrapper.find(StatisticsList);
+
+ expect(list.exists()).toBeTruthy();
+ expect(list.props('counts')).toBe(counts);
+ });
+
+ it('displays the commit duration chart', () => {
+ const chart = wrapper.find(GlColumnChart);
+
+ expect(chart.exists()).toBeTruthy();
+ expect(chart.props('yAxisTitle')).toBe('Minutes');
+ expect(chart.props('xAxisTitle')).toBe('Commit');
+ expect(chart.props('data')).toBe(wrapper.vm.timesChartTransformedData);
+ expect(chart.props('option')).toBe(wrapper.vm.$options.timesChartOptions);
+ });
+ });
+});
diff --git a/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js b/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
new file mode 100644
index 00000000000..93130f22407
--- /dev/null
+++ b/spec/frontend/projects/pipelines/charts/components/statistics_list_spec.js
@@ -0,0 +1,24 @@
+import { shallowMount } from '@vue/test-utils';
+import Component from '~/projects/pipelines/charts/components/statistics_list';
+import { counts } from '../mock_data';
+
+describe('StatisticsList', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(Component, {
+ propsData: {
+ counts,
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
+ });
+});
diff --git a/spec/frontend/projects/pipelines/charts/mock_data.js b/spec/frontend/projects/pipelines/charts/mock_data.js
new file mode 100644
index 00000000000..93e53125679
--- /dev/null
+++ b/spec/frontend/projects/pipelines/charts/mock_data.js
@@ -0,0 +1,11 @@
+export const counts = {
+ failed: 2,
+ success: 2,
+ total: 4,
+ successRatio: 50,
+};
+
+export const timesChartData = {
+ labels: ['as1234', 'kh423hy', 'ji56bvg', 'th23po'],
+ values: [5, 3, 7, 4],
+};
diff --git a/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
new file mode 100644
index 00000000000..a38aea7b972
--- /dev/null
+++ b/spec/lib/gitlab/alerting/notification_payload_parser_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Alerting::NotificationPayloadParser do
+ describe '.call' do
+ let(:starts_at) { Time.current.change(usec: 0) }
+ let(:payload) do
+ {
+ 'title' => 'alert title',
+ 'start_time' => starts_at.rfc3339,
+ 'description' => 'Description',
+ 'monitoring_tool' => 'Monitoring tool name',
+ 'service' => 'Service',
+ 'hosts' => ['gitlab.com']
+ }
+ end
+
+ subject { described_class.call(payload) }
+
+ it 'returns Prometheus-like payload' do
+ is_expected.to eq(
+ {
+ 'annotations' => {
+ 'title' => 'alert title',
+ 'description' => 'Description',
+ 'monitoring_tool' => 'Monitoring tool name',
+ 'service' => 'Service',
+ 'hosts' => ['gitlab.com']
+ },
+ 'startsAt' => starts_at.rfc3339
+ }
+ )
+ end
+
+ context 'when title is blank' do
+ before do
+ payload[:title] = ''
+ end
+
+ it 'sets a predefined title' do
+ expect(subject.dig('annotations', 'title')).to eq('New: Incident')
+ end
+ end
+
+ context 'when hosts attribute is a string' do
+ before do
+ payload[:hosts] = 'gitlab.com'
+ end
+
+ it 'returns hosts as an array of one element' do
+ expect(subject.dig('annotations', 'hosts')).to eq(['gitlab.com'])
+ end
+ end
+
+ context 'when the time is in unsupported format' do
+ before do
+ payload[:start_time] = 'invalid/date/format'
+ end
+
+ it 'sets startsAt to a current time in RFC3339 format' do
+ expect(subject['startsAt']).to eq(starts_at.rfc3339)
+ end
+ end
+
+ context 'when payload is blank' do
+ let(:payload) { {} }
+
+ it 'returns default parameters' do
+ is_expected.to eq(
+ 'annotations' => { 'title' => 'New: Incident' },
+ 'startsAt' => starts_at.rfc3339
+ )
+ end
+ end
+
+ context 'when payload attributes have blank lines' do
+ let(:payload) do
+ {
+ 'title' => '',
+ 'start_time' => '',
+ 'description' => '',
+ 'monitoring_tool' => '',
+ 'service' => '',
+ 'hosts' => ['']
+ }
+ end
+
+ it 'returns default parameters' do
+ is_expected.to eq(
+ 'annotations' => { 'title' => 'New: Incident' },
+ 'startsAt' => starts_at.rfc3339
+ )
+ end
+ end
+
+ context 'when payload has secondary params' do
+ let(:payload) do
+ {
+ 'description' => 'Description',
+ 'additional' => {
+ 'params' => {
+ '1' => 'Some value 1',
+ '2' => 'Some value 2',
+ 'blank' => ''
+ }
+ }
+ }
+ end
+
+ it 'adds secondary params to annotations' do
+ is_expected.to eq(
+ 'annotations' => {
+ 'title' => 'New: Incident',
+ 'description' => 'Description',
+ 'additional.params.1' => 'Some value 1',
+ 'additional.params.2' => 'Some value 2'
+ },
+ 'startsAt' => starts_at.rfc3339
+ )
+ end
+ end
+
+ context 'when secondary params hash is too big' do
+ before do
+ allow(Gitlab::Utils::SafeInlineHash).to receive(:merge_keys!).and_raise(ArgumentError)
+ end
+
+ it 'catches and re-raises an error' do
+ expect { subject }.to raise_error Gitlab::Alerting::NotificationPayloadParser::BadPayloadError, 'The payload is too big'
+ end
+ end
+ end
+end
diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb
index a123ff5a2a6..93abef063cb 100644
--- a/spec/models/prometheus_metric_spec.rb
+++ b/spec/models/prometheus_metric_spec.rb
@@ -67,6 +67,7 @@ describe PrometheusMetric do
it_behaves_like 'group_title', :business, 'Business metrics (Custom)'
it_behaves_like 'group_title', :response, 'Response metrics (Custom)'
it_behaves_like 'group_title', :system, 'System metrics (Custom)'
+ it_behaves_like 'group_title', :cluster_health, 'Cluster Health'
end
describe '#priority' do
@@ -82,6 +83,7 @@ describe PrometheusMetric do
:business | 0
:response | -5
:system | -10
+ :cluster_health | 10
end
with_them do
@@ -106,6 +108,7 @@ describe PrometheusMetric do
:business | %w()
:response | %w()
:system | %w()
+ :cluster_health | %w(container_memory_usage_bytes container_cpu_usage_seconds_total)
end
with_them do
diff --git a/spec/presenters/release_presenter_spec.rb b/spec/presenters/release_presenter_spec.rb
index 4c6142f2edb..82f312622ff 100644
--- a/spec/presenters/release_presenter_spec.rb
+++ b/spec/presenters/release_presenter_spec.rb
@@ -51,6 +51,22 @@ describe ReleasePresenter do
end
end
+ describe '#self_url' do
+ subject { presenter.self_url }
+
+ it 'returns its own url' do
+ is_expected.to match /#{project_release_url(project, release)}/
+ end
+
+ context 'when release_show_page feature flag is disabled' do
+ before do
+ stub_feature_flags(release_show_page: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
describe '#merge_requests_url' do
subject { presenter.merge_requests_url }