Skip to content
Snippets Groups Projects
Commit bcc77054 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 05b5c609
No related branches found
No related tags found
No related merge requests found
Showing
with 378 additions and 83 deletions
Loading
Loading
@@ -117,11 +117,7 @@ export default class FileTemplateMediator {
selector.hide();
}
});
if (this.editor.getValue() !== '') {
this.setTypeSelectorToggleText(item.name);
}
this.setTypeSelectorToggleText(item.name);
this.cacheToggleText();
}
 
Loading
Loading
Loading
Loading
@@ -16,7 +16,7 @@ export default {
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) {
Loading
Loading
Loading
Loading
@@ -14,7 +14,7 @@ class ContainerExpirationPolicy < ApplicationRecord
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
{
Loading
Loading
# 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
Loading
Loading
@@ -187,6 +187,7 @@ class Project < ApplicationRecord
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
Loading
Loading
@@ -316,6 +317,7 @@ class Project < ApplicationRecord
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
Loading
Loading
# 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
Loading
Loading
@@ -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
Loading
Loading
@@ -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:
Loading
Loading
Loading
Loading
@@ -60,6 +60,6 @@ module WorkerContext
end
 
def with_context(context, &block)
Gitlab::ApplicationContext.new(context).use(&block)
Gitlab::ApplicationContext.new(context).use { yield(**context) }
end
end
Loading
Loading
@@ -2,15 +2,17 @@
 
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|
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"
Loading
Loading
@@ -115,11 +115,14 @@ With this configuration, we:
- 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:
 
Loading
Loading
Loading
Loading
@@ -27,7 +27,7 @@ deploy rolling out. The percentage is the percent of the pods that are updated
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/)
Loading
Loading
@@ -37,7 +37,7 @@ knowledge. In particular you should be familiar with:
## 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
Loading
Loading
@@ -65,7 +65,7 @@ To display the Deploy Boards for a specific [environment] you should:
 
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).
Loading
Loading
# 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
Loading
Loading
@@ -23,16 +23,18 @@ describe 'Issue Detail', :js do
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)
wait_for_requests
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)
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
Loading
Loading
Loading
Loading
@@ -75,6 +75,11 @@ describe 'Projects > Files > Template type dropdown selector', :js do
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
Loading
Loading
Loading
Loading
@@ -103,7 +103,75 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
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
Loading
Loading
@@ -114,14 +182,9 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
let(:nodes) do
Project.order(Arel.sql('projects.last_repository_check_at IS NULL')).order(last_repository_check_at: :asc).order(id: :asc)
end
let(:ascending_nodes) { [project5, project1, project3, project2, project4] }
 
context 'when no cursor is passed' do
let(:arguments) { {} }
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) } }
Loading
Loading
@@ -131,14 +194,6 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
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) } }
 
Loading
Loading
@@ -146,36 +201,15 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
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
let(:descending_nodes) { [project3, project1, project5, project2, project4] }
 
context 'when no cursor is passed' do
let(:arguments) { {} }
it 'only returns projects in descending order' do
expect(subject.sliced_nodes).to eq([project3, project1, project5, project2, project4])
end
end
it_behaves_like 'nodes are in descending order'
 
context 'when before cursor value is NULL' do
let(:arguments) { { before: encoded_cursor(project4) } }
Loading
Loading
@@ -185,14 +219,6 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
end
end
 
context 'when before cursor value is not NULL' do
let(:arguments) { { before: encoded_cursor(project5) } }
it 'returns all projects before the cursor' do
expect(subject.sliced_nodes).to eq([project3, project1])
end
end
context 'when after cursor value is NULL' do
let(:arguments) { { after: encoded_cursor(project2) } }
 
Loading
Loading
@@ -200,22 +226,32 @@ describe Gitlab::Graphql::Connections::Keyset::Connection do
expect(subject.sliced_nodes).to eq([project4])
end
end
end
end
 
context 'when after cursor value is not NULL' do
let(:arguments) { { after: encoded_cursor(project1) } }
context 'when ordering uses LOWER' do
let!(:project1) { create(:project, name: 'A') } # Asc: project1 Desc: project4
let!(:project2) { create(:project, name: 'c') } # Asc: project5 Desc: project2
let!(:project3) { create(:project, name: 'b') } # Asc: project3 Desc: project3
let!(:project4) { create(:project, name: 'd') } # Asc: project2 Desc: project5
let!(:project5) { create(:project, name: 'a') } # Asc: project4 Desc: project1
 
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq([project5, project2, project4])
end
context 'when ascending' do
let(:nodes) do
Project.order(Arel::Table.new(:projects)['name'].lower.asc).order(id: :asc)
end
let(:ascending_nodes) { [project1, project5, project3, project2, project4] }
 
context 'when before and after cursor' do
let(:arguments) { { before: encoded_cursor(project4), after: encoded_cursor(project3) } }
it_behaves_like 'nodes are in ascending order'
end
 
it 'returns all projects after the cursor' do
expect(subject.sliced_nodes).to eq([project1, project5, project2])
end
context 'when descending' do
let(:nodes) do
Project.order(Arel::Table.new(:projects)['name'].lower.desc).order(id: :desc)
end
let(:descending_nodes) { [project4, project2, project3, project5, project1] }
it_behaves_like 'nodes are in descending order'
end
end
 
Loading
Loading
Loading
Loading
@@ -49,9 +49,9 @@ RSpec.describe ContainerExpirationPolicy, type: :model do
it 'preloads the associations' do
subject
 
query = ActiveRecord::QueryRecorder.new { subject.each(&:project) }
query = ActiveRecord::QueryRecorder.new { subject.map(&:project).map(&:full_path) }
 
expect(query.count).to eq(2)
expect(query.count).to eq(3)
end
end
 
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment