Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c27acb1d37
commit
0434f38ef1
3
Gemfile
3
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'
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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)}%
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Migrate CI CD statistics + duration chart to VueJS
|
||||
merge_request: 23840
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Task lists work correctly again on closed MRs
|
||||
merge_request: 23714
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Separate commit entities into own class files
|
||||
merge_request: 24085
|
||||
author: Rajendra Kadam
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
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.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 102 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 741 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 4.9 KiB |
|
|
@ -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**
|
||||
|
||||

|
||||
|
||||
|
|
@ -330,6 +329,33 @@ Note the following properties:
|
|||
|
||||

|
||||
|
||||
#### 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) |
|
||||
|
||||

|
||||
|
||||
##### 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
class CommitStats < Grape::Entity
|
||||
expose :additions, :deletions, :total
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
class CommitWithStats < Commit
|
||||
expose :stats, using: Entities::CommitStats
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
before do
|
||||
create(:project_snippet, project: project, author: user)
|
||||
allow(Snippet).to receive(:default_per_page).and_return(1)
|
||||
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
|
||||
|
||||
visit project_snippets_path(project)
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'paginated snippets'
|
||||
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
|
||||
end
|
||||
|
||||
it_behaves_like 'paginated snippets'
|
||||
end
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`;
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
@ -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],
|
||||
};
|
||||
|
|
@ -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
|
||||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue