Commit 903efabd authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 46209aef7c2c
......@@ -117,11 +117,7 @@
selector.hide();
}
});
if (this.editor.getValue() !== '') {
this.setTypeSelectorToggleText(item.name);
}
this.setTypeSelectorToggleText(item.name);
this.cacheToggleText();
}
......
......@@ -16,7 +16,7 @@
commit(types.REQUEST_DATA);
api
.lsifData(state.projectPath, state.commitId, state.path)
.lsifData(state.projectPath, state.commitId, state.blobPath)
.then(({ data }) => {
const normalizedData = data.reduce((acc, d) => {
if (d.hover) {
......
......@@ -14,7 +14,7 @@
validates :keep_n, inclusion: { in: ->(_) { self.keep_n_options.keys } }, allow_nil: true
scope :active, -> { where(enabled: true) }
scope :preloaded, -> { preload(:project) }
scope :preloaded, -> { preload(project: [:route]) }
def self.keep_n_options
{
......
# frozen_string_literal: true
module IncidentManagement
class ProjectIncidentManagementSetting < ApplicationRecord
include Gitlab::Utils::StrongMemoize
belongs_to :project
validate :issue_template_exists, if: :create_issue?
def available_issue_templates
Gitlab::Template::IssueTemplate.all(project)
end
def issue_template_content
strong_memoize(:issue_template_content) do
issue_template&.content if issue_template_key.present?
end
end
private
def issue_template_exists
return unless issue_template_key.present?
errors.add(:issue_template_key, 'not found') unless issue_template
end
def issue_template
Gitlab::Template::IssueTemplate.find(issue_template_key, project)
rescue Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
end
end
end
......@@ -187,6 +187,7 @@
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :project_repository, inverse_of: :project
has_one :incident_management_setting, inverse_of: :project, class_name: 'IncidentManagement::ProjectIncidentManagementSetting'
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
has_one :grafana_integration, inverse_of: :project
......@@ -316,6 +317,7 @@
allow_destroy: true,
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
accepts_nested_attributes_for :incident_management_setting, update_only: true
accepts_nested_attributes_for :error_tracking_setting, update_only: true
accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
accepts_nested_attributes_for :grafana_integration, update_only: true, allow_destroy: true
......
# frozen_string_literal: true
module IncidentManagement
class CreateIssueService < BaseService
include Gitlab::Utils::StrongMemoize
INCIDENT_LABEL = {
title: 'incident',
color: '#CC0033',
description: <<~DESCRIPTION.chomp
Denotes a disruption to IT services and \
the associated issues require immediate attention
DESCRIPTION
}.freeze
def initialize(project, params)
super(project, User.alert_bot, params)
end
def execute
return error_with('setting disabled') unless incident_management_setting.create_issue?
return error_with('invalid alert') unless alert.valid?
issue = create_issue
return error_with(issue_errors(issue)) unless issue.valid?
success(issue: issue)
end
private
def create_issue
issue = do_create_issue(label_ids: issue_label_ids)
# Create an unlabelled issue if we couldn't create the issue
# due to labels errors.
# See https://gitlab.com/gitlab-org/gitlab-foss/issues/65042
if issue.errors.include?(:labels)
log_label_error(issue)
issue = do_create_issue
end
issue
end
def do_create_issue(**params)
Issues::CreateService.new(
project,
current_user,
title: issue_title,
description: issue_description,
**params
).execute
end
def issue_title
alert.full_title
end
def issue_description
horizontal_line = "\n---\n\n"
[
alert_summary,
alert_markdown,
issue_template_content
].compact.join(horizontal_line)
end
def issue_label_ids
[
find_or_create_label(**INCIDENT_LABEL)
].compact.map(&:id)
end
def find_or_create_label(**params)
Labels::FindOrCreateService
.new(current_user, project, **params)
.execute
end
def alert_summary
alert.issue_summary_markdown
end
def alert_markdown
alert.alert_markdown
end
def alert
strong_memoize(:alert) do
Gitlab::Alerting::Alert.new(project: project, payload: params).present
end
end
def issue_template_content
incident_management_setting.issue_template_content
end
def incident_management_setting
strong_memoize(:incident_management_setting) do
project.incident_management_setting ||
project.build_incident_management_setting
end
end
def issue_errors(issue)
issue.errors.full_messages.to_sentence
end
def log_label_error(issue)
log_info <<~TEXT.chomp
Cannot create incident issue with labels \
#{issue.labels.map(&:title).inspect} \
for "#{project.full_name}": #{issue.errors.full_messages.to_sentence}.
Retrying without labels.
TEXT
end
def error_with(message)
log_error(%{Cannot create incident issue for "#{project.full_name}": #{message}})
error(message)
end
end
end
......@@ -10,7 +10,7 @@
#blob-content-holder.blob-content-holder
- if native_code_navigation_enabled?(@project)
#js-code-navigation{ data: { commit_id: blob.commit_id, path: blob.path, project_path: @project.full_path } }
#js-code-navigation{ data: { commit_id: blob.commit_id, blob_path: blob.path, project_path: @project.full_path } }
%article.file-holder
= render 'projects/blob/header', blob: blob
= render 'projects/blob/content', blob: blob
......@@ -429,6 +429,12 @@
:latency_sensitive:
:resource_boundary: :unknown
:weight: 1
- :name: incident_management:incident_management_process_alert
:feature_category: :incident_management
:has_external_dependencies:
:latency_sensitive:
:resource_boundary: :unknown
:weight: 2
- :name: mail_scheduler:mail_scheduler_issue_due
:feature_category: :issue_tracking
:has_external_dependencies:
......
......@@ -60,6 +60,6 @@
end
def with_context(context, &block)
Gitlab::ApplicationContext.new(context).use(&block)
Gitlab::ApplicationContext.new(context).use { yield(**context) }
end
end
......@@ -2,9 +2,9 @@
class ContainerExpirationPolicyWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
include CronjobQueue
feature_category :container_registry
def perform
ContainerExpirationPolicy.runnable_schedules.preloaded.find_each do |container_expiration_policy|
......@@ -6,11 +6,13 @@
feature_category :container_registry
def perform
ContainerExpirationPolicy.runnable_schedules.preloaded.find_each do |container_expiration_policy|
ContainerExpirationPolicyService.new(
container_expiration_policy.project, container_expiration_policy.project.owner
).execute(container_expiration_policy)
with_context(project: container_expiration_policy.project,
user: container_expiration_policy.project.owner) do |project:, user:|
ContainerExpirationPolicyService.new(project, user)
.execute(container_expiration_policy)
end
end
end
end
# frozen_string_literal: true
module IncidentManagement
class ProcessAlertWorker
include ApplicationWorker
queue_namespace :incident_management
feature_category :incident_management
def perform(project_id, alert)
project = find_project(project_id)
return unless project
create_issue(project, alert)
end
private
def find_project(project_id)
Project.find_by_id(project_id)
end
def create_issue(project, alert)
IncidentManagement::CreateIssueService
.new(project, alert)
.execute
end
end
end
---
title: Show selected template type when clicked
merge_request: 24596
author:
type: fixed
dashboard: 'Default dashboard'
priority: 1
panel_groups:
- group: Web Service
panels:
- title: Web Service - Error Ratio
type: line-chart
y_label: "Unhandled Exceptions (%)"
metrics:
- id: wser_web_service
query_range: 'max(max_over_time(gitlab_service_errors:ratio{environment="{{ci_environment_slug}}", type="web", stage="main"}[1m])) by (type) * 100'
unit: "%"
label: "Error Ratio"
- id: wser_degradation_slo
query_range: 'avg(slo:max:gitlab_service_errors:ratio{environment="{{ci_environment_slug}}", type="web", stage="main"}) or avg(slo:max:gitlab_service_errors:ratio{type="web"}) * 100'
unit: "%"
label: "Degradation SLO"
- id: wser_outage_slo
query_range: '2 * (avg(slo:max:gitlab_service_errors:ratio{environment="{{ci_environment_slug}}", type="web", stage="main"}) or avg(slo:max:gitlab_service_errors:ratio{type="web"})) * 100'
unit: "%"
label: "Outage SLO"
- group: API Service
panels:
- title: API Service - Error Ratio
type: line-chart
y_label: "Unhandled Exceptions (%)"
metrics:
- id: aser_web_service
query_range: 'max(max_over_time(gitlab_service_errors:ratio{environment="{{ci_environment_slug}}", type="api", stage="main"}[1m])) by (type) * 100'
unit: "%"
label: "Error Ratio"
- id: aser_degradation_slo
query_range: 'avg(slo:max:gitlab_service_errors:ratio{environment="{{ci_environment_slug}}", type="api", stage="main"}) or avg(slo:max:gitlab_service_errors:ratio{type="web"}) * 100'
unit: "%"
label: "Degradation SLO"
- id: aser_outage_slo
query_range: '2 * (avg(slo:max:gitlab_service_errors:ratio{environment="{{ci_environment_slug}}", type="api", stage="main"}) or avg(slo:max:gitlab_service_errors:ratio{type="web"})) * 100'
unit: "%"
label: "Outage SLO"
......@@ -115,11 +115,14 @@
- Lastly we deploy to the staging server.
NOTE: **Note:**
The `environment` keyword is just a hint for GitLab that this job actually
deploys to the `name` environment. It can also have a `url` that is
exposed in various places within GitLab. Each time a job that
has an environment specified succeeds, a deployment is recorded, storing
the Git SHA and environment name.
The `environment` keyword defines where the app is deployed.
The environment `name` and `url` is exposed in various places
within GitLab. Each time a job that has an environment specified
succeeds, a deployment is recorded, along with the Git SHA and environment name.
CAUTION: **Caution**:
Some characters are not allowed in environment names. Use only letters,
numbers, spaces, and `-`, `_`, `/`, `{`, `}`, or `.`. Also, it must not start nor end with `/`.
In summary, with the above `.gitlab-ci.yml` we have achieved the following:
......
......@@ -27,7 +27,7 @@
to the latest release.
Since Deploy Boards are tightly coupled with Kubernetes, there is some required
knowledge. In particular you should be familiar with:
knowledge. In particular, you should be familiar with:
- [Kubernetes pods](https://kubernetes.io/docs/concepts/workloads/pods/pod/)
- [Kubernetes labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/)
......@@ -37,7 +37,7 @@
## Use cases
Since the Deploy Board is a visual representation of the Kubernetes pods for a
specific environment, there are lot of uses cases. To name a few:
specific environment, there are a lot of use cases. To name a few:
- You want to promote what's running in staging, to production. You go to the
environments list, verify that what's running in staging is what you think is
......@@ -65,7 +65,7 @@
NOTE: **Running on OpenShift:**
If you are using OpenShift, ensure that you're using the `Deployment` resource
instead of `DeploymentConfiguration`, otherwise the Deploy Boards won't render
instead of `DeploymentConfiguration`. Otherwise, the Deploy Boards won't render
correctly. For more information, read the
[OpenShift docs](https://docs.openshift.com/container-platform/3.7/dev_guide/deployments/kubernetes_deployments.html#kubernetes-deployments-vs-deployment-configurations)
and [GitLab issue #4584](https://gitlab.com/gitlab-org/gitlab/issues/4584).
......
# frozen_string_literal: true
FactoryBot.define do
factory :project_incident_management_setting, class: 'IncidentManagement::ProjectIncidentManagementSetting' do
project
create_issue { false }
issue_template_key { nil }
send_email { false }
end
end
......@@ -23,5 +23,6 @@
context 'when issue description has xss snippet' do
before do
issue.update!(description: '![xss" onload=alert(1);//](a)')
sign_in(user)
visit project_issue_path(project, issue)
......@@ -26,7 +27,6 @@
sign_in(user)
visit project_issue_path(project, issue)
wait_for_requests
end
it 'encodes the description to prevent xss issues' do
page.within('.issuable-details .detail-page-description') do
......@@ -29,5 +29,7 @@
end
it 'encodes the description to prevent xss issues' do
page.within('.issuable-details .detail-page-description') do
image = find('img.js-lazy-loaded')
expect(page).to have_selector('img', count: 1)
......@@ -33,6 +35,6 @@
expect(page).to have_selector('img', count: 1)
expect(find('img')['onerror']).to be_nil
expect(find('img')['src']).to end_with('/a')
expect(image['onerror']).to be_nil
expect(image['src']).to end_with('/a')
end
end
end
......
......@@ -75,6 +75,11 @@
check_type_selector_toggle_text('.gitignore')
end
it 'sets the toggle text when selecting the template type' do
select_template_type('.gitignore')
check_type_selector_toggle_text('.gitignore')
end
it 'selects every template type correctly' do
try_selecting_all_types
end
......
......@@ -103,7 +103,75 @@
end
end
context 'when multiple orders are defined' do
shared_examples 'nodes are in ascending order' do
context 'when no cursor is passed' do
let(:arguments) { {} }
it 'returns projects in ascending order' do
expect(subject.sliced_nodes).to eq(ascending_nodes)
end
end
context 'when before cursor value is not NULL' do
let(:arguments) { { before: encoded_cursor(ascending_nodes[2]) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq(ascending_nodes.first(2))
end
end
context 'when after cursor value is not NULL' do
let(:arguments) { { after: encoded_cursor(ascending_nodes[1]) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(ascending_nodes.last(3))
end
end
context 'when before and after cursor' do
let(:arguments) { { before: encoded_cursor(ascending_nodes.last), after: encoded_cursor(ascending_nodes.first) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(ascending_nodes[1..3])
end
end
end
shared_examples 'nodes are in descending order' do
context 'when no cursor is passed' do
let(:arguments) { {} }
it 'only returns projects in descending order' do
expect(subject.sliced_nodes).to eq(descending_nodes)
end
end
context 'when before cursor value is not NULL' do
let(:arguments) { { before: encoded_cursor(descending_nodes[2]) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq(descending_nodes.first(2))
end
end
context 'when after cursor value is not NULL' do
let(:arguments) { { after: encoded_cursor(descending_nodes[1]) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(descending_nodes.last(3))
end
end
context 'when before and after cursor' do
let(:arguments) { { before: encoded_cursor(descending_nodes.last), after: encoded_cursor(descending_nodes.first) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq(descending_nodes[1..3])
end
end
end
context 'when multiple orders with nil values are defined' do
let!(:project1) { create(:project, last_repository_check_at: 10.days.ago) } # Asc: project5 Desc: project3
let!(:project2) { create(:project, last_repository_check_at: nil) } # Asc: project1 Desc: project1
let!(:project3) { create(:project, last_repository_check_at: 5.days.ago) } # Asc: project3 Desc: project5
......@@ -114,7 +182,5 @@
let(:nodes) do
Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :asc).order(id: :asc)
end
context 'when no cursor is passed' do
let(:arguments) { {} }
let(:ascending_nodes) { [project5, project1, project3, project2, project4] }
......@@ -120,8 +186,5 @@
it 'returns projects in ascending order' do
expect(subject.sliced_nodes).to eq([project5, project1, project3, project2, project4])
end
end
it_behaves_like 'nodes are in ascending order'
context 'when before cursor value is NULL' do
let(:arguments) { { before: encoded_cursor(project4) } }
......@@ -131,14 +194,6 @@
end
end
context 'when before cursor value is not NULL' do
let(:arguments) { { before: encoded_cursor(project3) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq([project5, project1])
end
end
context 'when after cursor value is NULL' do
let(:arguments) { { after: encoded_cursor(project2) } }
......@@ -146,25 +201,9 @@
expect(subject.sliced_nodes).to eq([project4])
end
end
context 'when after cursor value is not NULL' do
let(:arguments) { { after: encoded_cursor(project1) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq([project3, project2, project4])
end
end
context 'when before and after cursor' do
let(:arguments) { { before: encoded_cursor(project4), after: encoded_cursor(project5) } }
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq([project1, project3, project2])
end
end
end
context 'when descending' do
let(:nodes) do
Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :desc).order(id: :asc)
end
......@@ -165,10 +204,8 @@
end
context 'when descending' do
let(:nodes) do
Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :desc).order(id: :asc)