Commit c52b81f4 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 187ee320
......@@ -43,16 +43,16 @@ code_quality:
# We need to duplicate this job's definition because it seems it's impossible to
# override an included `only.refs`.
# See https://gitlab.com/gitlab-org/gitlab/issues/31371.
# Once https://gitlab.com/gitlab-org/gitlab/merge_requests/16487 will be deployed
# to GitLab.com, we should be able to use the template and set SAST_DISABLE_DIND: "true".
sast:
.sast:
extends:
- .default-retry
- .reports:rules:sast
- .use-docker-in-docker
stage: test
allow_failure: true
# `needs: []` starts the job immediately in the pipeline
# https://docs.gitlab.com/ee/ci/yaml/README.html#needs
needs: []
allow_failure: true
artifacts:
paths:
- gl-sast-report.json # GitLab-specific
......@@ -63,22 +63,39 @@ sast:
# emptying DOCKER_HOST so it can be detected properly on kubernetes executor
# with the script below
DOCKER_HOST: ""
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
SAST_ANALYZER_IMAGE_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
SAST_ANALYZER_IMAGE_TAG: 2
SAST_BRAKEMAN_LEVEL: 2 # GitLab-specific
SAST_EXCLUDED_PATHS: qa,spec,doc,ee/spec # GitLab-specific
script:
- export SAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
- |
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
export DOCKER_HOST='tcp://localhost:2375'
fi
fi
- |
ENVS=`printenv | grep -vE '^(DOCKER_|CI|GITLAB_|FF_|HOME|PWD|OLDPWD|PATH|SHLVL|HOSTNAME)' | sed -n '/^[^\t]/s/=.*//p' | sed '/^$/d' | sed 's/^/-e /g' | tr '\n' ' '`
docker run "$ENVS" \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
"registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
- /analyzer run
brakeman-sast:
extends: .sast
image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/brakeman:$SAST_ANALYZER_IMAGE_TAG"
eslint-sast:
extends: .sast
image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG"
kubesec-sast:
extends: .sast
image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/kubesec:$SAST_ANALYZER_IMAGE_TAG"
nodejs-scan-sast:
extends: .sast
image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/nodejs-scan:$SAST_ANALYZER_IMAGE_TAG"
secrets-sast:
extends: .sast
image:
name: "$SAST_ANALYZER_IMAGE_PREFIX/secrets:$SAST_ANALYZER_IMAGE_TAG"
# We need to duplicate this job's definition because it seems it's impossible to
# override an included `only.refs`.
......
......@@ -87,11 +87,17 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] }
return {
name: 'deployments',
value: [deployment.createdAt, annotationsYAxisCoords.pos],
// style options
symbol: deployment.icon,
symbolSize: symbolSizes.default,
itemStyle: {
color: deployment.color,
},
// metadata that are accessible in `formatTooltipText` method
tooltipData: {
sha: deployment.sha.substring(0, 8),
commitUrl: deployment.commitUrl,
},
};
});
......@@ -100,8 +106,12 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] }
return {
name: 'annotations',
value: [annotation.from, annotationsYAxisCoords.pos],
// style options
symbol: 'none',
description: annotation.description,
// metadata that are accessible in `formatTooltipText` method
tooltipData: {
description: annotation.description,
},
};
});
......
......@@ -262,19 +262,17 @@ export default {
params.seriesData.forEach(dataPoint => {
if (dataPoint.value) {
const [xVal, yVal] = dataPoint.value;
const [, yVal] = dataPoint.value;
this.tooltip.type = dataPoint.name;
if (this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.deployments)) {
const [deploy] = this.recentDeployments.filter(
deployment => deployment.createdAt === xVal,
);
this.tooltip.sha = deploy.sha.substring(0, 8);
this.tooltip.commitUrl = deploy.commitUrl;
const { data = {} } = dataPoint;
this.tooltip.sha = data?.tooltipData?.sha;
this.tooltip.commitUrl = data?.tooltipData?.commitUrl;
} else if (
this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.annotations)
) {
const { data } = dataPoint;
this.tooltip.content.push(data?.description);
this.tooltip.content.push(data?.tooltipData?.description);
} else {
const { seriesName, color, dataIndex } = dataPoint;
......
......@@ -210,3 +210,15 @@
}
}
}
.health-status {
.dropdown-body {
.health-divider {
border-top-color: $gray-200;
}
.dropdown-item:not(.health-dropdown-item) {
padding: 0;
}
}
}
......@@ -3,7 +3,7 @@
module Projects
module Prometheus
class AlertPresenter < Gitlab::View::Presenter::Delegated
RESERVED_ANNOTATIONS = %w(gitlab_incident_markdown title).freeze
RESERVED_ANNOTATIONS = %w(gitlab_incident_markdown gitlab_y_label title).freeze
GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze
MARKDOWN_LINE_BREAK = " \n".freeze
INCIDENT_LABEL_NAME = IncidentManagement::CreateIssueService::INCIDENT_LABEL[:title].freeze
......
......@@ -57,7 +57,7 @@ module Metrics
# @return [Hash]
override :raw_dashboard
def raw_dashboard
panels_not_found!(identifiers) if panels.empty?
panels_not_found!(identifiers) if metrics.empty?
{ 'panel_groups' => [{ 'panels' => panels }] }
end
......@@ -66,11 +66,20 @@ module Metrics
# Generated dashboard panels for each metric which
# matches the provided input.
#
# As the panel is generated
# on the fly, we're using default values for info
# not represented in the DB.
#
# @return [Array<Hash>]
def panels
strong_memoize(:panels) do
metrics.map { |metric| panel_for_metric(metric) }
end
[{
type: DEFAULT_PANEL_TYPE,
weight: DEFAULT_PANEL_WEIGHT,
title: title,
y_label: y_label,
metrics: metrics.map(&:to_metric_hash)
}]
end
# Metrics which match the provided inputs.
......@@ -78,12 +87,14 @@ module Metrics
# displayed in a single panel/chart.
# @return [ActiveRecord::AssociationRelation<PromtheusMetric>]
def metrics
PrometheusMetricsFinder.new(
project: project,
group: group_key,
title: title,
y_label: y_label
).execute
strong_memoize(:metrics) do
PrometheusMetricsFinder.new(
project: project,
group: group_key,
title: title,
y_label: y_label
).execute
end
end
# Returns a symbol representing the group that
......@@ -101,22 +112,6 @@ module Metrics
.to_s
end
end
# Returns a representation of a PromtheusMetric
# as a dashboard panel. As the panel is generated
# on the fly, we're using default values for info
# not represented in the DB.
#
# @return [Hash]
def panel_for_metric(metric)
{
type: DEFAULT_PANEL_TYPE,
weight: DEFAULT_PANEL_WEIGHT,
title: metric.title,
y_label: metric.y_label,
metrics: [metric.to_metric_hash]
}
end
end
end
end
---
title: Show multimetric embeds on a single chart
merge_request: 28841
author:
type: fixed
# frozen_string_literal: true
class CreatePypiPackageMetadata < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :packages_pypi_metadata, id: false do |t|
t.references :package, primary_key: true, index: false, default: nil, foreign_key: { to_table: :packages_packages, on_delete: :cascade }, type: :bigint
t.string "required_python", null: false, limit: 50
end
end
end
......@@ -4493,6 +4493,11 @@ CREATE SEQUENCE public.packages_packages_id_seq
ALTER SEQUENCE public.packages_packages_id_seq OWNED BY public.packages_packages.id;
CREATE TABLE public.packages_pypi_metadata (
package_id bigint NOT NULL,
required_python character varying(50) NOT NULL
);
CREATE TABLE public.packages_tags (
id bigint NOT NULL,
package_id integer NOT NULL,
......@@ -8146,6 +8151,9 @@ ALTER TABLE ONLY public.packages_package_files
ALTER TABLE ONLY public.packages_packages
ADD CONSTRAINT packages_packages_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.packages_pypi_metadata
ADD CONSTRAINT packages_pypi_metadata_pkey PRIMARY KEY (package_id);
ALTER TABLE ONLY public.packages_tags
ADD CONSTRAINT packages_tags_pkey PRIMARY KEY (id);
......@@ -11596,6 +11604,9 @@ ALTER TABLE ONLY public.board_labels
ALTER TABLE ONLY public.scim_identities
ADD CONSTRAINT fk_rails_9421a0bffb FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.packages_pypi_metadata
ADD CONSTRAINT fk_rails_9698717cdd FOREIGN KEY (package_id) REFERENCES public.packages_packages(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.packages_dependency_links
ADD CONSTRAINT fk_rails_96ef1c00d3 FOREIGN KEY (package_id) REFERENCES public.packages_packages(id) ON DELETE CASCADE;
......@@ -13032,6 +13043,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200318164448
20200318165448
20200318175008
20200318183553
20200319071702
20200319123041
20200319124127
......
......@@ -467,6 +467,11 @@ input CreateEpicInput {
"""
clientMutationId: String
"""
Indicates if the epic is confidential. Will be ignored if `confidential_epics` feature flag is disabled
"""
confidential: Boolean
"""
The description of the epic
"""
......@@ -2014,6 +2019,11 @@ type Epic implements Noteable {
"""
closedAt: Time
"""
Indicates if the epic is confidential
"""
confidential: Boolean
"""
Timestamp of the epic's creation
"""
......@@ -8903,6 +8913,11 @@ input UpdateEpicInput {
"""
clientMutationId: String
"""
Indicates if the epic is confidential. Will be ignored if `confidential_epics` feature flag is disabled
"""
confidential: Boolean
"""
The description of the epic
"""
......
......@@ -1403,6 +1403,16 @@
},
"defaultValue": null
},
{
"name": "confidential",
"description": "Indicates if the epic is confidential. Will be ignored if `confidential_epics` feature flag is disabled",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": null
},
{
"name": "startDateFixed",
"description": "The start date of the epic",
......@@ -5932,6 +5942,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "confidential",
"description": "Indicates if the epic is confidential",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createdAt",
"description": "Timestamp of the epic's creation",
......@@ -27002,6 +27026,16 @@
},
"defaultValue": null
},
{
"name": "confidential",
"description": "Indicates if the epic is confidential. Will be ignored if `confidential_epics` feature flag is disabled",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": null
},
{
"name": "startDateFixed",
"description": "The start date of the epic",
......
......@@ -335,6 +335,7 @@ Represents an epic.
| --- | ---- | ---------- |
| `author` | User! | Author of the epic |
| `closedAt` | Time | Timestamp of the epic's closure |
| `confidential` | Boolean | Indicates if the epic is confidential |
| `createdAt` | Time | Timestamp of the epic's creation |
| `descendantCounts` | EpicDescendantCount | Number of open and closed descendant epics and issues |
| `descendantWeightSum` | EpicDescendantWeights | Total weight of open and closed issues in the epic and its descendants |
......
......@@ -138,6 +138,4 @@ Incident Management features can be easily enabled & disabled via the Project se
#### Auto-creation
GitLab Issues can automatically be created as a result of an Alert notification. An Issue created this way will contain error information to help you further debug the error.
For [GitLab-managed alerting rules](../project/integrations/prometheus.md#setting-up-alerts-for-prometheus-metrics-ultimate), the issue will include an embedded chart for the query corresponding to the alert. The chart will show an hour of data surrounding the starting point of the incident, 30 minutes before and after.
You can automatically create GitLab issues from an Alert notification. Issues created this way contain error information to help you debug the error. Appropriately configured alerts include an [embedded chart](../project/integrations/prometheus.md#embedding-metrics-based-on-alerts-in-incident-issues) for the query corresponding to the alert.
......@@ -802,6 +802,22 @@ It is also possible to embed either the default dashboard metrics or individual
![Embedded Metrics in issue templates](img/embed_metrics_issue_template.png)
### Embedding metrics based on alerts in incident issues
For [GitLab-managed alerting rules](#setting-up-alerts-for-prometheus-metrics-ultimate), the issue will include an embedded chart for the query corresponding to the alert. The chart displays an hour of data surrounding the starting point of the incident, 30 minutes before and after.
For [manually configured Prometheus instances](#manual-configuration-of-prometheus), a chart corresponding to the query can be included if these requirements are met:
- The alert corresponds to an environment managed through GitLab.
- The alert corresponds to a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries).
- The alert contains the required attributes listed in the chart below.
| Attributes | Required | Description |
| ---------- | -------- | ----------- |
| `annotations/gitlab_environment_name` | Yes | Name of the GitLab-managed environment corresponding to the alert |
| One of `annotations/title`, `annotations/summary`, `labels/alertname` | Yes | Will be used as the chart title |
| `annotations/gitlab_y_label` | No | Will be used as the chart's y-axis label |
### Embedding Cluster Health Charts **(ULTIMATE)**
> [Introduced](<https://gitlab.com/gitlab-org/gitlab/issues/40997>) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.9.
......
......@@ -177,7 +177,7 @@ that's progressing as planned or needs attention to keep on schedule:
- **Needs attention** (amber)
- **At risk** (red)
!["On track" health status on an issue](img/issue_health_status_v12_10.png)
!["On track" health status on an issue](img/issue_health_status_dropdown_v12_10.png)
You can then see issue statuses on the
[Epic tree](../../group/epics/index.md#issue-health-status-in-epic-tree-ultimate).
......
......@@ -69,6 +69,12 @@ module Gitlab
end
end
def y_label
strong_memoize(:y_label) do
parse_y_label_from_payload || title
end
end
def alert_markdown
strong_memoize(:alert_markdown) do
parse_alert_markdown_from_payload
......@@ -162,6 +168,10 @@ module Gitlab
def parse_alert_markdown_from_payload
payload&.dig('annotations', 'gitlab_incident_markdown')
end
def parse_y_label_from_payload
payload&.dig('annotations', 'gitlab_y_label')
end
end
end
end
......@@ -3850,9 +3850,6 @@ msgstr ""
msgid "Choose which shards you wish to synchronize to this secondary node"
msgstr ""
msgid "Choose which status most accurately reflects the current state of this issue:"
msgstr ""
msgid "Choose your framework"
msgstr ""
......@@ -16950,6 +16947,9 @@ msgstr ""
msgid "Rename/Move"
msgstr ""
msgid "Reopen"
msgstr ""
msgid "Reopen epic"
msgstr ""
......@@ -17145,6 +17145,9 @@ msgstr ""
msgid "Requirement"
msgstr ""
msgid "Requirement title cannot have more than %{limit} characters."
msgstr ""
msgid "Requirements"
msgstr ""
......@@ -18040,6 +18043,9 @@ msgstr ""
msgid "Select groups to replicate"
msgstr ""
msgid "Select health status"
msgstr ""
msgid "Select labels"
msgstr ""
......@@ -18576,16 +18582,22 @@ msgstr ""
msgid "Side-by-side"
msgstr ""
msgid "Sidebar|Assign health status"
msgstr ""
msgid "Sidebar|Change weight"
msgstr ""
msgid "Sidebar|None"
msgid "Sidebar|Health status"
msgstr ""
msgid "Sidebar|Only numeral characters allowed"
msgid "Sidebar|No status"
msgstr ""
msgid "Sidebar|None"
msgstr ""
msgid "Sidebar|Status"
msgid "Sidebar|Only numeral characters allowed"
msgstr ""
msgid "Sidebar|Weight"
......@@ -18813,6 +18825,9 @@ msgstr ""
msgid "Something went wrong while applying the suggestion. Please try again."
msgstr ""
msgid "Something went wrong while archiving a requirement."
msgstr ""
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr ""
......@@ -18885,6 +18900,9 @@ msgstr ""
msgid "Something went wrong while performing the action."
msgstr ""
msgid "Something went wrong while reopening a requirement."
msgstr ""
msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr ""
......@@ -24957,9 +24975,6 @@ msgstr ""
msgid "remove due date"
msgstr ""
msgid "remove status"
msgstr ""
msgid "remove weight"
msgstr ""
......
......@@ -155,43 +155,56 @@ describe('Time series component', () => {
describe('methods', () => {
describe('formatTooltipText', () => {
let mockDate;
let mockCommitUrl;
let generateSeriesData;
beforeEach(() => {
mockDate = deploymentData[0].created_at;
mockCommitUrl = deploymentData[0].commitUrl;
generateSeriesData = type => ({
seriesData: [
{
seriesName: timeSeriesChart.vm.chartData[0].name,
componentSubType: type,
value: [mockDate, 5.55555],
dataIndex: 0,
...(type === 'scatter' && { name: 'deployments' }),
},
],
value: mockDate,
});
const mockCommitUrl = deploymentData[0].commitUrl;
const mockDate = deploymentData[0].created_at;
const mockSha = 'f5bcd1d9';
const mockLineSeriesData = () => ({
seriesData: [
{
seriesName: timeSeriesChart.vm.chartData[0].name,
componentSubType: 'line',
value: [mockDate, 5.55555],
dataIndex: 0,
},
],
value: mockDate,
});
const annotationsMetadata = {
tooltipData: {
sha: mockSha,
commitUrl: mockCommitUrl,
},
};
const mockAnnotationsSeriesData = {
seriesData: [
{
componentSubType: 'scatter',
seriesName: 'series01',
dataIndex: 0,
value: [mockDate, 5.55555],
type: 'scatter',
name: 'deployments',
},
],
value: mockDate,
};
it('does not throw error if data point is outside the zoom range', () => {
const seriesDataWithoutValue = generateSeriesData('line');
expect(
timeSeriesChart.vm.formatTooltipText({
...seriesDataWithoutValue,
seriesData: seriesDataWithoutValue.seriesData.map(data => ({
...data,
value: undefined,
})),
}),
).toBeUndefined();
const seriesDataWithoutValue = {
...mockLineSeriesData(),
seriesData: mockLineSeriesData().seriesData.map(data => ({
...data,
value: undefined,
})),
};
expect(timeSeriesChart.vm.formatTooltipText(seriesDataWithoutValue)).toBeUndefined();
});
describe('when series is of line type', () => {
beforeEach(done => {
timeSeriesChart.vm.formatTooltipText(generateSeriesData('line'));
timeSeriesChart.vm.formatTooltipText(mockLineSeriesData());
timeSeriesChart.vm.$nextTick(done);
});
......@@ -223,7 +236,14 @@ describe('Time series component', () => {
describe('when series is of scatter type, for deployments', () => {
beforeEach(() => {
timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter'));
timeSeriesChart.vm.formatTooltipText({
...mockAnnotationsSeriesData,
seriesData: mockAnnotationsSeriesData.seriesData.map(data => ({
...data,
data: annotationsMetadata,
})),
});
return timeSeriesChart.vm.$nextTick;
});
it('set tooltip type to deployments', () => {
......@@ -242,6 +262,25 @@ describe('Time series component', () => {
expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl);
});
});
describe('when series is of scatter type and deployments data is missing', () => {
beforeEach(() => {
timeSeriesChart.vm.formatTooltipText(mockAnnotationsSeriesData);
return timeSeriesChart.vm.$nextTick;
});
it('formats tooltip title', () => {
expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM');
});
it('formats tooltip sha', () => {
expect(timeSeriesChart.vm.tooltip.sha).toBeUndefined();
});
it('formats tooltip commit url', () => {
expect(timeSeriesChart.vm.tooltip.commitUrl).toBeUndefined();
});
});
});
describe('setSvg', () => {
......
......@@ -192,6 +192,16 @@ describe Gitlab::Alerting::Alert do
end
end
describe '#y_label' do
subject { alert.y_label }
it_behaves_like 'parse payload', 'annotations/gitlab_y_label'
context 'when y_label is not included in the payload' do
it_behaves_like 'parse payload', 'annotations/title'
end
end
describe '#alert_markdown' do
subject { alert.alert_markdown }
......
......@@ -121,11 +121,18 @@ describe Metrics::Dashboard::CustomMetricEmbedService do
it_behaves_like 'valid embedded dashboard service response'
it 'includes both metrics' do
it 'includes both metrics in a single panel' do
result = service_call
included_queries = all_queries(result[:dashboard])
expect(included_queries).to include('avg(metric_2)', 'avg(metric)')
panel_groups = result[:dashboard][:panel_groups]
panels = panel_groups[0][:panels]
metrics = panels[0][:metrics]
queries = metrics.map { |metric| metric[:query_range] }
expect(panel_groups.length).to eq(1)
expect(panels.length).to eq(1)
expect(metrics.length).to eq(2)
expect(queries).to include('avg(metric_2)', 'avg(metric)')
end
end
end
......@@ -136,16 +143,4 @@ describe Metrics::Dashboard::CustomMetricEmbedService do
it_behaves_like 'misconfigured dashboard service response', :not_found
end
end
private
def all_queries(dashboard)
dashboard[:panel_groups].flat_map do |group|
group[:panels].flat_map do |panel|
panel[:metrics].map do |metric|
metric[:query_range]
end
end
end
end
end
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