diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-31 18:09:11 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-01-31 18:09:11 +0000 |
commit | 0434f38ef1dce4fe640fe1e4542235746ceb943c (patch) | |
tree | 3affe5902c9da74441dfbf5069f76c023b5cd03a | |
parent | c27acb1d376f7127cd33eadcc8f5683ed55262bc (diff) | |
download | gitlab-ce-0434f38ef1dce4fe640fe1e4542235746ceb943c.tar.gz |
Add latest changes from gitlab-org/gitlab@master
59 files changed, 820 insertions, 203 deletions
@@ -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 Binary files differdeleted file mode 100644 index 566e0519fbe..00000000000 --- a/doc/user/project/img/labels_delete_v12_1.png +++ /dev/null diff --git a/doc/user/project/img/labels_list_v12_1.png b/doc/user/project/img/labels_list_v12_1.png Binary files differdeleted file mode 100644 index 47359d05f7f..00000000000 --- a/doc/user/project/img/labels_list_v12_1.png +++ /dev/null diff --git a/doc/user/project/img/labels_new_label_from_sidebar.gif b/doc/user/project/img/labels_new_label_from_sidebar.gif Binary files differdeleted file mode 100644 index 572b29a86e1..00000000000 --- a/doc/user/project/img/labels_new_label_from_sidebar.gif +++ /dev/null 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 Binary files differnew file mode 100644 index 00000000000..985f2b04ef3 --- /dev/null +++ b/doc/user/project/integrations/img/prometheus_dashboard_column_panel_type.png diff --git a/doc/user/project/integrations/img/prometheus_service_configuration.png b/doc/user/project/integrations/img/prometheus_service_configuration.png Binary files differindex c7dfe874817..a38d1bce197 100644 --- a/doc/user/project/integrations/img/prometheus_service_configuration.png +++ b/doc/user/project/integrations/img/prometheus_service_configuration.png 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 } |