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: ...@@ -43,16 +43,16 @@ code_quality:
# We need to duplicate this job's definition because it seems it's impossible to # We need to duplicate this job's definition because it seems it's impossible to
# override an included `only.refs`. # override an included `only.refs`.
# See https://gitlab.com/gitlab-org/gitlab/issues/31371. # See https://gitlab.com/gitlab-org/gitlab/issues/31371.
# Once https://gitlab.com/gitlab-org/gitlab/merge_requests/16487 will be deployed .sast:
# to GitLab.com, we should be able to use the template and set SAST_DISABLE_DIND: "true".
sast:
extends: extends:
- .default-retry - .default-retry
- .reports:rules:sast - .reports:rules:sast
- .use-docker-in-docker - .use-docker-in-docker
stage: test stage: test
allow_failure: true # `needs: []` starts the job immediately in the pipeline
# https://docs.gitlab.com/ee/ci/yaml/README.html#needs
needs: [] needs: []
allow_failure: true
artifacts: artifacts:
paths: paths:
- gl-sast-report.json # GitLab-specific - gl-sast-report.json # GitLab-specific
...@@ -63,22 +63,39 @@ sast: ...@@ -63,22 +63,39 @@ sast:
# emptying DOCKER_HOST so it can be detected properly on kubernetes executor # emptying DOCKER_HOST so it can be detected properly on kubernetes executor
# with the script below # with the script below
DOCKER_HOST: "" 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_BRAKEMAN_LEVEL: 2 # GitLab-specific
SAST_EXCLUDED_PATHS: qa,spec,doc,ee/spec # GitLab-specific SAST_EXCLUDED_PATHS: qa,spec,doc,ee/spec # GitLab-specific
script: script:
- export SAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')} - /analyzer run
- |
if ! docker info &>/dev/null; then brakeman-sast:
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then extends: .sast
export DOCKER_HOST='tcp://localhost:2375' image:
fi name: "$SAST_ANALYZER_IMAGE_PREFIX/brakeman:$SAST_ANALYZER_IMAGE_TAG"
fi
- | eslint-sast:
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' ' '` extends: .sast
docker run "$ENVS" \ image:
--volume "$PWD:/code" \ name: "$SAST_ANALYZER_IMAGE_PREFIX/eslint:$SAST_ANALYZER_IMAGE_TAG"
--volume /var/run/docker.sock:/var/run/docker.sock \
"registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code 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 # We need to duplicate this job's definition because it seems it's impossible to
# override an included `only.refs`. # override an included `only.refs`.
......
...@@ -87,11 +87,17 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] } ...@@ -87,11 +87,17 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] }
return { return {
name: 'deployments', name: 'deployments',
value: [deployment.createdAt, annotationsYAxisCoords.pos], value: [deployment.createdAt, annotationsYAxisCoords.pos],
// style options
symbol: deployment.icon, symbol: deployment.icon,
symbolSize: symbolSizes.default, symbolSize: symbolSizes.default,
itemStyle: { itemStyle: {
color: deployment.color, 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 = [] } ...@@ -100,8 +106,12 @@ export const generateAnnotationsSeries = ({ deployments = [], annotations = [] }
return { return {
name: 'annotations', name: 'annotations',
value: [annotation.from, annotationsYAxisCoords.pos], value: [annotation.from, annotationsYAxisCoords.pos],
// style options
symbol: 'none', symbol: 'none',
description: annotation.description, // metadata that are accessible in `formatTooltipText` method
tooltipData: {
description: annotation.description,
},
}; };
}); });
......
...@@ -262,19 +262,17 @@ export default { ...@@ -262,19 +262,17 @@ export default {
params.seriesData.forEach(dataPoint => { params.seriesData.forEach(dataPoint => {
if (dataPoint.value) { if (dataPoint.value) {
const [xVal, yVal] = dataPoint.value; const [, yVal] = dataPoint.value;
this.tooltip.type = dataPoint.name; this.tooltip.type = dataPoint.name;
if (this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.deployments)) { if (this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.deployments)) {
const [deploy] = this.recentDeployments.filter( const { data = {} } = dataPoint;
deployment => deployment.createdAt === xVal, this.tooltip.sha = data?.tooltipData?.sha;
); this.tooltip.commitUrl = data?.tooltipData?.commitUrl;
this.tooltip.sha = deploy.sha.substring(0, 8);
this.tooltip.commitUrl = deploy.commitUrl;
} else if ( } else if (
this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.annotations) this.isTooltipOfType(this.tooltip.type, this.$options.tooltipTypes.annotations)
) { ) {
const { data } = dataPoint; const { data } = dataPoint;
this.tooltip.content.push(data?.description); this.tooltip.content.push(data?.tooltipData?.description);
} else { } else {
const { seriesName, color, dataIndex } = dataPoint; const { seriesName, color, dataIndex } = dataPoint;
......
...@@ -210,3 +210,15 @@ ...@@ -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 @@ ...@@ -3,7 +3,7 @@
module Projects module Projects
module Prometheus module Prometheus
class AlertPresenter < Gitlab::View::Presenter::Delegated 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 GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze
MARKDOWN_LINE_BREAK = " \n".freeze MARKDOWN_LINE_BREAK = " \n".freeze
INCIDENT_LABEL_NAME = IncidentManagement::CreateIssueService::INCIDENT_LABEL[:title].freeze INCIDENT_LABEL_NAME = IncidentManagement::CreateIssueService::INCIDENT_LABEL[:title].freeze
......
...@@ -57,7 +57,7 @@ module Metrics ...@@ -57,7 +57,7 @@ module Metrics
# @return [Hash] # @return [Hash]
override :raw_dashboard override :raw_dashboard
def raw_dashboard def raw_dashboard
panels_not_found!(identifiers) if panels.empty? panels_not_found!(identifiers) if metrics.empty?
{ 'panel_groups' => [{ 'panels' => panels }] } { 'panel_groups' => [{ 'panels' => panels }] }
end end
...@@ -66,11 +66,20 @@ module Metrics ...@@ -66,11 +66,20 @@ module Metrics
# Generated dashboard panels for each metric which # Generated dashboard panels for each metric which
# matches the provided input. # 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>] # @return [Array<Hash>]
def panels def panels
strong_memoize(:panels) do [{
metrics.map { |metric| panel_for_metric(metric) } type: DEFAULT_PANEL_TYPE,
end weight: DEFAULT_PANEL_WEIGHT,
title: title,
y_label: y_label,
metrics: metrics.map(&:to_metric_hash)
}]
end end
# Metrics which match the provided inputs. # Metrics which match the provided inputs.
...@@ -78,12 +87,14 @@ module Metrics ...@@ -78,12 +87,14 @@ module Metrics
# displayed in a single panel/chart. # displayed in a single panel/chart.
# @return [ActiveRecord::AssociationRelation<PromtheusMetric>] # @return [ActiveRecord::AssociationRelation<PromtheusMetric>]
def metrics def metrics
PrometheusMetricsFinder.new( strong_memoize(:metrics) do
project: project, PrometheusMetricsFinder.new(
group: group_key, project: project,
title: title, group: group_key,
y_label: y_label title: title,
).execute y_label: y_label
).execute
end
end end
# Returns a symbol representing the group that # Returns a symbol representing the group that
...@@ -101,22 +112,6 @@ module Metrics ...@@ -101,22 +112,6 @@ module Metrics
.to_s .to_s
end end
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 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 ...@@ -4493,6 +4493,11 @@ CREATE SEQUENCE public.packages_packages_id_seq
ALTER SEQUENCE public.packages_packages_id_seq OWNED BY public.packages_packages.id; 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 ( CREATE TABLE public.packages_tags (
id bigint NOT NULL, id bigint NOT NULL,
package_id integer NOT NULL, package_id integer NOT NULL,
...@@ -8146,6 +8151,9 @@ ALTER TABLE ONLY public.packages_package_files ...@@ -8146,6 +8151,9 @@ ALTER TABLE ONLY public.packages_package_files
ALTER TABLE ONLY public.packages_packages ALTER TABLE ONLY public.packages_packages
ADD CONSTRAINT packages_packages_pkey PRIMARY KEY (id); 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 ALTER TABLE ONLY public.packages_tags
ADD CONSTRAINT packages_tags_pkey PRIMARY KEY (id); ADD CONSTRAINT packages_tags_pkey PRIMARY KEY (id);
...@@ -11596,6 +11604,9 @@ ALTER TABLE ONLY public.board_labels ...@@ -11596,6 +11604,9 @@ ALTER TABLE ONLY public.board_labels
ALTER TABLE ONLY public.scim_identities ALTER TABLE ONLY public.scim_identities
ADD CONSTRAINT fk_rails_9421a0bffb FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; 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 ALTER TABLE ONLY public.packages_dependency_links
ADD CONSTRAINT fk_rails_96ef1c00d3 FOREIGN KEY (package_id) REFERENCES public.packages_packages(id) ON DELETE CASCADE; 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; ...@@ -13032,6 +13043,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200318164448 20200318164448
20200318165448 20200318165448
20200318175008 20200318175008
20200318183553
20200319071702 20200319071702
20200319123041 20200319123041
20200319124127 20200319124127
......
...@@ -467,6 +467,11 @@ input CreateEpicInput { ...@@ -467,6 +467,11 @@ input CreateEpicInput {
""" """
clientMutationId: String 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 The description of the epic
""" """
...@@ -2014,6 +2019,11 @@ type Epic implements Noteable { ...@@ -2014,6 +2019,11 @@ type Epic implements Noteable {
""" """
closedAt: Time closedAt: Time
"""
Indicates if the epic is confidential
"""
confidential: Boolean
""" """
Timestamp of the epic's creation Timestamp of the epic's creation
""" """
...@@ -8903,6 +8913,11 @@ input UpdateEpicInput { ...@@ -8903,6 +8913,11 @@ input UpdateEpicInput {
""" """
clientMutationId: String 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 The description of the epic
""" """
......
...@@ -1403,6 +1403,16 @@ ...@@ -1403,6 +1403,16 @@
}, },
"defaultValue": null "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", "name": "startDateFixed",
"description": "The start date of the epic", "description": "The start date of the epic",
...@@ -5932,6 +5942,20 @@ ...@@ -5932,6 +5942,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "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", "name": "createdAt",
"description": "Timestamp of the epic's creation", "description": "Timestamp of the epic's creation",
...@@ -27002,6 +27026,16 @@ ...@@ -27002,6 +27026,16 @@
}, },
"defaultValue": null "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", "name": "startDateFixed",
"description": "The start date of the epic", "description": "The start date of the epic",
......
...@@ -335,6 +335,7 @@ Represents an epic. ...@@ -335,6 +335,7 @@ Represents an epic.
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `author` | User! | Author of the epic | | `author` | User! | Author of the epic |
| `closedAt` | Time | Timestamp of the epic's closure | | `closedAt` | Time | Timestamp of the epic's closure |
| `confidential` | Boolean | Indicates if the epic is confidential |
| `createdAt` | Time | Timestamp of the epic's creation | | `createdAt` | Time | Timestamp of the epic's creation |
| `descendantCounts` | EpicDescendantCount | Number of open and closed descendant epics and issues | | `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 | | `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 ...@@ -138,6 +138,4 @@ Incident Management features can be easily enabled & disabled via the Project se
#### Auto-creation #### 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. 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.
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.
...@@ -802,6 +802,22 @@ It is also possible to embed either the default dashboard metrics or individual ...@@ -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) ![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)** ### Embedding Cluster Health Charts **(ULTIMATE)**
> [Introduced](<https://gitlab.com/gitlab-org/gitlab/issues/40997>) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.9. > [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: ...@@ -177,7 +177,7 @@ that's progressing as planned or needs attention to keep on schedule:
- **Needs attention** (amber) - **Needs attention** (amber)
- **At risk** (red) - **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 You can then see issue statuses on the
[Epic tree](../../group/epics/index.md#issue-health-status-in-epic-tree-ultimate). [Epic tree](../../group/epics/index.md#issue-health-status-in-epic-tree-ultimate).
......
...@@ -69,6 +69,12 @@ module Gitlab ...@@ -69,6 +69,12 @@ module Gitlab
end end
end end
def y_label
strong_memoize(:y_label) do
parse_y_label_from_payload || title
end
end
def alert_markdown def alert_markdown
strong_memoize(:alert_markdown) do strong_memoize(:alert_markdown) do
parse_alert_markdown_from_payload parse_alert_markdown_from_payload
...@@ -162,6 +168,10 @@ module Gitlab ...@@ -162,6 +168,10 @@ module Gitlab
def parse_alert_markdown_from_payload def parse_alert_markdown_from_payload
payload&.dig('annotations', 'gitlab_incident_markdown') payload&.dig('annotations', 'gitlab_incident_markdown')
end end
def parse_y_label_from_payload
payload&.dig('annotations', 'gitlab_y_label')
end
end end
end end
end end
...@@ -3850,9 +3850,6 @@ msgstr "" ...@@ -3850,9 +3850,6 @@ msgstr ""
msgid "Choose which shards you wish to synchronize to this secondary node" msgid "Choose which shards you wish to synchronize to this secondary node"
msgstr "" msgstr ""
msgid "Choose which status most accurately reflects the current state of this issue:"
msgstr ""
msgid "Choose your framework" msgid "Choose your framework"
msgstr "" msgstr ""
...@@ -16950,6 +16947,9 @@ msgstr "" ...@@ -16950,6 +16947,9 @@ msgstr ""
msgid "Rename/Move" msgid "Rename/Move"
msgstr "" msgstr ""
msgid "Reopen"
msgstr ""
msgid "Reopen epic" msgid "Reopen epic"
msgstr "" msgstr ""
...@@ -17145,6 +17145,9 @@ msgstr "" ...@@ -17145,6 +17145,9 @@ msgstr ""
msgid "Requirement" msgid "Requirement"
msgstr "" msgstr ""
msgid "Requirement title cannot have more than %{limit} characters."
msgstr ""
msgid "Requirements" msgid "Requirements"
msgstr "" msgstr ""
...@@ -18040,6 +18043,9 @@ msgstr "" ...@@ -18040,6 +18043,9 @@ msgstr ""
msgid "Select groups to replicate" msgid "Select groups to replicate"
msgstr "" msgstr ""
msgid "Select health status"
msgstr ""
msgid "Select labels" msgid "Select labels"
msgstr "" msgstr ""
...@@ -18576,16 +18582,22 @@ msgstr "" ...@@ -18576,16 +18582,22 @@ msgstr ""
msgid "Side-by-side" msgid "Side-by-side"
msgstr "" msgstr ""
msgid "Sidebar|Assign health status"
msgstr ""
msgid "Sidebar|Change weight" msgid "Sidebar|Change weight"
msgstr "" msgstr ""
msgid "Sidebar|None" msgid "Sidebar|Health status"
msgstr "" msgstr ""
msgid "Sidebar|Only numeral characters allowed" msgid "Sidebar|No status"
msgstr ""
msgid "Sidebar|None"
msgstr "" msgstr ""
msgid "Sidebar|Status" msgid "Sidebar|Only numeral characters allowed"
msgstr "" msgstr ""
msgid "Sidebar|Weight" msgid "Sidebar|Weight"
...@@ -18813,6 +18825,9 @@ msgstr "" ...@@ -18813,6 +18825,9 @@ msgstr ""
msgid "Something went wrong while applying the suggestion. Please try again." msgid "Something went wrong while applying the suggestion. Please try again."
msgstr "" msgstr ""
msgid "Something went wrong while archiving a requirement."
msgstr ""
msgid "Something went wrong while closing the %{issuable}. Please try again later" msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr "" msgstr ""
...@@ -18885,6 +18900,9 @@ msgstr "" ...@@ -18885,6 +18900,9 @@ msgstr ""
msgid "Something went wrong while performing the action." msgid "Something went wrong while performing the action."
msgstr "" msgstr ""
msgid "Something went wrong while reopening a requirement."
msgstr ""
msgid "Something went wrong while reopening the %{issuable}. Please try again later" msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr "" msgstr ""
...@@ -24957,9 +24975,6 @@ msgstr "" ...@@ -24957,9 +24975,6 @@ msgstr ""
msgid "remove due date" msgid "remove due date"
msgstr "" msgstr ""
msgid "remove status"
msgstr ""
msgid "remove weight" msgid "remove weight"
msgstr "" msgstr ""
......
...@@ -155,43 +155,56 @@ describe('Time series component', () => { ...@@ -155,43 +155,56 @@ describe('Time series component', () => {
describe('methods', () => { describe('methods', () => {
describe('formatTooltipText', () => { describe('formatTooltipText', () => {
let mockDate; const mockCommitUrl = deploymentData[0].commitUrl;
let mockCommitUrl; const mockDate = deploymentData[0].created_at;
let generateSeriesData; const mockSha = 'f5bcd1d9';
const mockLineSeriesData = () => ({
beforeEach(() => { seriesData: [
mockDate = deploymentData[0].created_at; {
mockCommitUrl = deploymentData[0].commitUrl; seriesName: timeSeriesChart.vm.chartData[0].name,
generateSeriesData = type => ({ componentSubType: 'line',
seriesData: [ value: [mockDate, 5.55555],
{ dataIndex: 0,
seriesName: timeSeriesChart.vm.chartData[0].name, },
componentSubType: type, ],
value: [mockDate, 5.55555], value: mockDate,
dataIndex: 0,
...(type === 'scatter' && { name: 'deployments' }),
},
],
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', () => { it('does not throw error if data point is outside the zoom range', () => {
const seriesDataWithoutValue = generateSeriesData('line'); const seriesDataWithoutValue = {
expect( ...mockLineSeriesData(),
timeSeriesChart.vm.formatTooltipText({ seriesData: mockLineSeriesData().seriesData.map(data => ({
...seriesDataWithoutValue, ...data,
seriesData: seriesDataWithoutValue.seriesData.map(data => ({ value: undefined,
...data, })),
value: undefined, };
})), expect(timeSeriesChart.vm.formatTooltipText(seriesDataWithoutValue)).toBeUndefined();
}),
).toBeUndefined();
}); });
describe('when series is of line type', () => { describe('when series is of line type', () => {
beforeEach(done => { beforeEach(done => {
timeSeriesChart.vm.formatTooltipText(generateSeriesData('line')); timeSeriesChart.vm.formatTooltipText(mockLineSeriesData());
timeSeriesChart.vm.$nextTick(done); timeSeriesChart.vm.$nextTick(done);
}); });
...@@ -223,7 +236,14 @@ describe('Time series component', () => { ...@@ -223,7 +236,14 @@ describe('Time series component', () => {
describe('when series is of scatter type, for deployments', () => { describe('when series is of scatter type, for deployments', () => {
beforeEach(() => { 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', () => { it('set tooltip type to deployments', () => {
...@@ -242,6 +262,25 @@ describe('Time series component', () => { ...@@ -242,6 +262,25 @@ describe('Time series component', () => {
expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl); 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', () => { describe('setSvg', () => {
......
...@@ -192,6 +192,16 @@ describe Gitlab::Alerting::Alert do ...@@ -192,6 +192,16 @@ describe Gitlab::Alerting::Alert do
end end
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 describe '#alert_markdown' do
subject { alert.alert_markdown } subject { alert.alert_markdown }
......
...@@ -121,11 +121,18 @@ describe Metrics::Dashboard::CustomMetricEmbedService do ...@@ -121,11 +121,18 @@ describe Metrics::Dashboard::CustomMetricEmbedService do
it_behaves_like 'valid embedded dashboard service response' 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 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 end
end end
...@@ -136,16 +143,4 @@ describe Metrics::Dashboard::CustomMetricEmbedService do ...@@ -136,16 +143,4 @@ describe Metrics::Dashboard::CustomMetricEmbedService do
it_behaves_like 'misconfigured dashboard service response', :not_found it_behaves_like 'misconfigured dashboard service response', :not_found
end end
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 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