Commit 0434f38e authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent c27acb1d
......@@ -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'
......@@ -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
......
......@@ -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',
......
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);
<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>
<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>
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;
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),
},
},
}),
});
};
......@@ -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"
......
/*
* 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;
}
......@@ -63,6 +63,6 @@ module PageLimiter
controller: params[:controller],
action: params[:action],
bot: dd.bot?
)
).increment
end
end
......@@ -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')
......@@ -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')
......@@ -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?
......
- 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'
%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'
%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)}%
%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
- 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
......@@ -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
---
title: Migrate CI CD statistics + duration chart to VueJS
merge_request: 23840
author:
type: changed
---
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
---
title: Task lists work correctly again on closed MRs
merge_request: 23714
author:
type: fixed
---
title: Separate commit entities into own class files
merge_request: 24085
author: Rajendra Kadam
type: added
---
title: Fix database permission check for triggers on Amazon RDS
merge_request: 24035
author:
type: fixed
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -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.
......
......@@ -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:
......
......@@ -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
......
# 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
# 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
# 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
# frozen_string_literal: true
module API
module Entities
class CommitStats < Grape::Entity
expose :additions, :deletions, :total
end
end
end
# frozen_string_literal: true
module API
module Entities
class CommitWithStats < Commit
expose :stats, using: Entities::CommitStats
end
end
end
# frozen_string_literal: true
module API
module Entities
class DiffRefs < Grape::Entity
expose :base_sha, :head_sha, :start_sha
end
end
end
# 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
......@@ -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
......
......@@ -6,5 +6,3 @@ module Gitlab
end
end
end
Gitlab::DatabaseImporters::CommonMetrics.prepend_if_ee('EE::Gitlab::DatabaseImporters::CommonMetrics')
......@@ -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')
......@@ -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 ""
......
......@@ -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
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
......
// 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>
`;
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);
});
});
});
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();
});
});
export const counts = {
failed: 2,
success: 2,
total: 4,
successRatio: 50,
};
export const timesChartData = {
labels: ['as1234', 'kh423hy', 'ji56bvg', 'th23po'],
values: [5, 3, 7, 4],
};
# 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
......@@ -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
......
......@@ -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 }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment