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

Add latest changes from gitlab-org/gitlab@master

parent 0434f38e
No related branches found
No related tags found
No related merge requests found
Showing
with 544 additions and 141 deletions
:mailboxes:
<%
require_relative "../lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
config = Gitlab::MailRoom.config
if Gitlab::MailRoom.enabled?
Gitlab::MailRoom.enabled_configs.each do |config|
%>
-
:host: <%= config[:host].to_json %>
Loading
Loading
@@ -24,8 +22,8 @@
:delivery_options:
:redis_url: <%= config[:redis_url].to_json %>
:namespace: <%= Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE %>
:queue: email_receiver
:worker: EmailReceiverWorker
:queue: <%= config[:queue] %>
:worker: <%= config[:worker] %>
<% if config[:sentinels] %>
:sentinels:
<% config[:sentinels].each do |sentinel| %>
Loading
Loading
Loading
Loading
@@ -8,7 +8,7 @@ namespace :explore do
 
resources :groups, only: [:index]
resources :snippets, only: [:index]
root to: 'projects#trending'
root to: 'projects#index'
end
 
# Compatibility with old routing
Loading
Loading
Loading
Loading
@@ -224,6 +224,8 @@
- 2
- - self_monitoring_project_delete
- 2
- - service_desk_email_receiver
- 1
- - system_hook_push
- 1
- - todos_destroyer
Loading
Loading
Loading
Loading
@@ -200,7 +200,7 @@ with the added complexity of many more nodes to configure, manage, and monitor.
 
![Fully Distributed architecture diagram](img/fully-distributed.png)
 
## Reference Architecture Examples
## Reference Architecture Recommendations
 
The Support and Quality teams build, performance test, and validate Reference
Architectures that support large numbers of users. The specifications below are
Loading
Loading
Loading
Loading
@@ -6298,6 +6298,16 @@ type SentryErrorCollection {
id: ID!
): SentryDetailedError
 
"""
Stack Trace of Sentry Error
"""
errorStackTrace(
"""
ID of the Sentry issue
"""
id: ID!
): SentryErrorStackTrace
"""
Collection of Sentry Errors
"""
Loading
Loading
@@ -6386,6 +6396,71 @@ type SentryErrorFrequency {
time: Time!
}
 
"""
An object containing a stack trace entry for a Sentry error.
"""
type SentryErrorStackTrace {
"""
Time the stack trace was received by Sentry
"""
dateReceived: String!
"""
ID of the Sentry error
"""
issueId: String!
"""
Stack trace entries for the Sentry error
"""
stackTraceEntries: [SentryErrorStackTraceEntry!]!
}
"""
An object context for a Sentry error stack trace
"""
type SentryErrorStackTraceContext {
"""
Code number of the context
"""
code: String!
"""
Line number of the context
"""
line: Int!
}
"""
An object containing a stack trace entry for a Sentry error.
"""
type SentryErrorStackTraceEntry {
"""
Function in which the Sentry error occurred
"""
col: String
"""
File in which the Sentry error occurred
"""
fileName: String
"""
Function in which the Sentry error occurred
"""
function: String
"""
Function in which the Sentry error occurred
"""
line: String
"""
Context of the Sentry error
"""
traceContext: [SentryErrorStackTraceContext!]
}
"""
State of a Sentry error
"""
Loading
Loading
Loading
Loading
@@ -17454,6 +17454,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errorStackTrace",
"description": "Stack Trace of Sentry Error",
"args": [
{
"name": "id",
"description": "ID of the Sentry issue",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "SentryErrorStackTrace",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Collection of Sentry Errors",
Loading
Loading
@@ -17984,6 +18011,221 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryErrorStackTrace",
"description": "An object containing a stack trace entry for a Sentry error.",
"fields": [
{
"name": "dateReceived",
"description": "Time the stack trace was received by Sentry",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issueId",
"description": "ID of the Sentry error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "stackTraceEntries",
"description": "Stack trace entries for the Sentry error",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SentryErrorStackTraceEntry",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryErrorStackTraceEntry",
"description": "An object containing a stack trace entry for a Sentry error.",
"fields": [
{
"name": "col",
"description": "Function in which the Sentry error occurred",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "fileName",
"description": "File in which the Sentry error occurred",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "function",
"description": "Function in which the Sentry error occurred",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "line",
"description": "Function in which the Sentry error occurred",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "traceContext",
"description": "Context of the Sentry error",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SentryErrorStackTraceContext",
"ofType": null
}
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SentryErrorStackTraceContext",
"description": "An object context for a Sentry error stack trace",
"fields": [
{
"name": "code",
"description": "Code number of the context",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "line",
"description": "Line number of the context",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Metadata",
Loading
Loading
Loading
Loading
@@ -983,6 +983,7 @@ An object containing a collection of Sentry errors, and a detailed error.
| Name | Type | Description |
| --- | ---- | ---------- |
| `detailedError` | SentryDetailedError | Detailed version of a Sentry error on the project |
| `errorStackTrace` | SentryErrorStackTrace | Stack Trace of Sentry Error |
| `errors` | SentryErrorConnection | Collection of Sentry Errors |
| `externalUrl` | String | External URL for Sentry |
 
Loading
Loading
@@ -993,6 +994,37 @@ An object containing a collection of Sentry errors, and a detailed error.
| `count` | Int! | Count of errors received since the previously recorded time |
| `time` | Time! | Time the error frequency stats were recorded |
 
## SentryErrorStackTrace
An object containing a stack trace entry for a Sentry error.
| Name | Type | Description |
| --- | ---- | ---------- |
| `dateReceived` | String! | Time the stack trace was received by Sentry |
| `issueId` | String! | ID of the Sentry error |
| `stackTraceEntries` | SentryErrorStackTraceEntry! => Array | Stack trace entries for the Sentry error |
## SentryErrorStackTraceContext
An object context for a Sentry error stack trace
| Name | Type | Description |
| --- | ---- | ---------- |
| `code` | String! | Code number of the context |
| `line` | Int! | Line number of the context |
## SentryErrorStackTraceEntry
An object containing a stack trace entry for a Sentry error.
| Name | Type | Description |
| --- | ---- | ---------- |
| `col` | String | Function in which the Sentry error occurred |
| `fileName` | String | File in which the Sentry error occurred |
| `function` | String | Function in which the Sentry error occurred |
| `line` | String | Function in which the Sentry error occurred |
| `traceContext` | SentryErrorStackTraceContext! => Array | Context of the Sentry error |
## SentryErrorTags
 
State of a Sentry error
Loading
Loading
Loading
Loading
@@ -128,75 +128,6 @@ module API
end
end
 
class BasicRef < Grape::Entity
expose :type, :name
end
class Branch < Grape::Entity
expose :name
expose :commit, using: Entities::Commit do |repo_branch, options|
options[:project].repository.commit(repo_branch.dereferenced_target)
end
expose :merged do |repo_branch, options|
if options[:merged_branch_names]
options[:merged_branch_names].include?(repo_branch.name)
else
options[:project].repository.merged_to_root_ref?(repo_branch)
end
end
expose :protected do |repo_branch, options|
::ProtectedBranch.protected?(options[:project], repo_branch.name)
end
expose :developers_can_push do |repo_branch, options|
::ProtectedBranch.developers_can?(:push, repo_branch.name, protected_refs: options[:project].protected_branches)
end
expose :developers_can_merge do |repo_branch, options|
::ProtectedBranch.developers_can?(:merge, repo_branch.name, protected_refs: options[:project].protected_branches)
end
expose :can_push do |repo_branch, options|
Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name)
end
expose :default do |repo_branch, options|
options[:project].default_branch == repo_branch.name
end
end
class TreeObject < Grape::Entity
expose :id, :name, :type, :path
expose :mode do |obj, options|
filemode = obj.mode
filemode = "0" + filemode if filemode.length < 6
filemode
end
end
class Snippet < Grape::Entity
expose :id, :title, :file_name, :description, :visibility
expose :author, using: Entities::UserBasic
expose :updated_at, :created_at
expose :project_id
expose :web_url do |snippet|
Gitlab::UrlBuilder.build(snippet)
end
end
class ProjectSnippet < Snippet
end
class PersonalSnippet < Snippet
expose :raw_url do |snippet|
Gitlab::UrlBuilder.build(snippet, raw: true)
end
end
class IssuableEntity < Grape::Entity
expose :id, :iid
expose(:project_id) { |entity| entity&.project.try(:id) }
Loading
Loading
# frozen_string_literal: true
module API
module Entities
class BasicRef < Grape::Entity
expose :type, :name
end
end
end
# frozen_string_literal: true
module API
module Entities
class Branch < Grape::Entity
expose :name
expose :commit, using: Entities::Commit do |repo_branch, options|
options[:project].repository.commit(repo_branch.dereferenced_target)
end
expose :merged do |repo_branch, options|
if options[:merged_branch_names]
options[:merged_branch_names].include?(repo_branch.name)
else
options[:project].repository.merged_to_root_ref?(repo_branch)
end
end
expose :protected do |repo_branch, options|
::ProtectedBranch.protected?(options[:project], repo_branch.name)
end
expose :developers_can_push do |repo_branch, options|
::ProtectedBranch.developers_can?(:push, repo_branch.name, protected_refs: options[:project].protected_branches)
end
expose :developers_can_merge do |repo_branch, options|
::ProtectedBranch.developers_can?(:merge, repo_branch.name, protected_refs: options[:project].protected_branches)
end
expose :can_push do |repo_branch, options|
Gitlab::UserAccess.new(options[:current_user], project: options[:project]).can_push_to_branch?(repo_branch.name)
end
expose :default do |repo_branch, options|
options[:project].default_branch == repo_branch.name
end
end
end
end
# frozen_string_literal: true
module API
module Entities
class PersonalSnippet < Snippet
expose :raw_url do |snippet|
Gitlab::UrlBuilder.build(snippet, raw: true)
end
end
end
end
# frozen_String_literal: true
module API
module Entities
class ProjectSnippet < Entities::Snippet
end
end
end
# frozen_string_literal: true
module API
module Entities
class Snippet < Grape::Entity
expose :id, :title, :file_name, :description, :visibility
expose :author, using: Entities::UserBasic
expose :updated_at, :created_at
expose :project_id
expose :web_url do |snippet|
Gitlab::UrlBuilder.build(snippet)
end
end
end
end
# frozen_string_literal: true
module API
module Entities
class TreeObject < Grape::Entity
expose :id, :name, :type, :path
expose :mode do |obj, options|
filemode = obj.mode
filemode = "0" + filemode if filemode.length < 6
filemode
end
end
end
end
Loading
Loading
@@ -5,7 +5,11 @@ module Gitlab
class ErrorEvent
include ActiveModel::Model
 
attr_accessor :issue_id, :date_received, :stack_trace_entries
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project
def self.declarative_policy_class
'ErrorTracking::BasePolicy'
end
end
end
end
Loading
Loading
@@ -2,6 +2,7 @@
 
require 'yaml'
require 'json'
require 'pathname'
require_relative 'redis/queues' unless defined?(Gitlab::Redis::Queues)
 
# This service is run independently of the main Rails process,
Loading
Loading
@@ -21,39 +22,60 @@ module Gitlab
log_path: RAILS_ROOT_DIR.join('log', 'mail_room_json.log')
}.freeze
 
# Email specific configuration which is merged with configuration
# fetched from YML config file.
ADDRESS_SPECIFIC_CONFIG = {
incoming_email: {
queue: 'email_receiver',
worker: 'EmailReceiverWorker'
},
service_desk_email: {
queue: 'service_desk_email_receiver',
worker: 'ServiceDeskEmailReceiverWorker'
}
}.freeze
class << self
def enabled?
config[:enabled] && config[:address]
def enabled_configs
@enabled_configs ||= configs.select { |config| enabled?(config) }
end
 
def config
@config ||= fetch_config
end
private
 
def reset_config!
@config = nil
def enabled?(config)
config[:enabled] && !config[:address].to_s.empty?
end
 
private
def configs
ADDRESS_SPECIFIC_CONFIG.keys.map { |key| fetch_config(key) }
end
 
def fetch_config
def fetch_config(config_key)
return {} unless File.exist?(config_file)
 
config = load_from_yaml || {}
config = DEFAULT_CONFIG.merge(config) do |_key, oldval, newval|
config = merged_configs(config_key)
config.merge!(redis_config) if enabled?(config)
config[:log_path] = File.expand_path(config[:log_path], RAILS_ROOT_DIR)
config
end
def merged_configs(config_key)
yml_config = load_yaml.fetch(config_key, {})
specific_config = ADDRESS_SPECIFIC_CONFIG.fetch(config_key, {})
DEFAULT_CONFIG.merge(specific_config, yml_config) do |_key, oldval, newval|
newval.nil? ? oldval : newval
end
end
 
if config[:enabled] && config[:address]
gitlab_redis_queues = Gitlab::Redis::Queues.new(rails_env)
config[:redis_url] = gitlab_redis_queues.url
def redis_config
gitlab_redis_queues = Gitlab::Redis::Queues.new(rails_env)
config = { redis_url: gitlab_redis_queues.url }
 
if gitlab_redis_queues.sentinels?
config[:sentinels] = gitlab_redis_queues.sentinels
end
if gitlab_redis_queues.sentinels?
config[:sentinels] = gitlab_redis_queues.sentinels
end
 
config[:log_path] = File.expand_path(config[:log_path], RAILS_ROOT_DIR)
config
end
 
Loading
Loading
@@ -65,8 +87,8 @@ module Gitlab
ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] || File.expand_path('../../config/gitlab.yml', __dir__)
end
 
def load_from_yaml
YAML.load_file(config_file)[rails_env].deep_symbolize_keys[:incoming_email]
def load_yaml
@yaml ||= YAML.load_file(config_file)[rails_env].deep_symbolize_keys
end
end
end
Loading
Loading
Loading
Loading
@@ -20259,6 +20259,9 @@ msgstr ""
msgid "Undo"
msgstr ""
 
msgid "Undo ignore"
msgstr ""
msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr ""
 
Loading
Loading
@@ -20310,6 +20313,9 @@ msgstr ""
msgid "Unmarks this %{noun} as Work In Progress."
msgstr ""
 
msgid "Unresolve"
msgstr ""
msgid "Unresolve discussion"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -26,30 +26,19 @@ module QA
group_id = fetch_group_id
 
sub_groups_head_response = head Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", per_page: "100").url
total_sub_groups = sub_groups_head_response.headers[:x_total]
total_sub_group_pages = sub_groups_head_response.headers[:x_total_pages]
 
STDOUT.puts "total_sub_groups: #{total_sub_groups}"
STDOUT.puts "total_sub_group_pages: #{total_sub_group_pages}"
sub_group_ids = fetch_subgroup_ids(group_id, total_sub_group_pages)
STDOUT.puts "Number of Sub Groups not already marked for deletion: #{sub_group_ids.length}"
 
total_sub_group_pages.to_i.times do |page_no|
# Fetch all subgroups for the top level group
sub_groups_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", per_page: "100").url
sub_group_ids = JSON.parse(sub_groups_response.body).map { |subgroup| subgroup["id"] }
if sub_group_ids.any?
STDOUT.puts "\n==== Current Page: #{page_no + 1} ====\n"
delete_subgroups(sub_group_ids)
end
end
delete_subgroups(sub_group_ids) unless sub_group_ids.empty?
STDOUT.puts "\nDone"
end
 
private
 
def delete_subgroups(sub_group_ids)
STDOUT.puts "Deleting #{sub_group_ids.length} subgroups..."
sub_group_ids.each do |subgroup_id|
delete_response = delete Runtime::API::Request.new(@api_client, "/groups/#{subgroup_id}").url
dot_or_f = delete_response.code == 202 ? "\e[32m.\e[0m" : "\e[31mF\e[0m"
Loading
Loading
@@ -61,6 +50,17 @@ module QA
group_search_response = get Runtime::API::Request.new(@api_client, "/groups", search: ENV['GROUP_NAME_OR_PATH'] || 'gitlab-qa-sandbox-group').url
JSON.parse(group_search_response.body).first["id"]
end
def fetch_subgroup_ids(group_id, group_pages)
sub_groups_ids = []
group_pages.to_i.times do |page_no|
sub_groups_response = get Runtime::API::Request.new(@api_client, "/groups/#{group_id}/subgroups", page: (page_no + 1).to_s, per_page: "100").url
sub_groups_ids.concat(JSON.parse(sub_groups_response.body).reject { |subgroup| !subgroup["marked_for_deletion_on"].nil? }.map { |subgroup| subgroup["id"] })
end
sub_groups_ids.uniq
end
end
end
end
Loading
Loading
@@ -39,39 +39,31 @@ describe 'mail_room.yml' do
end
end
 
context 'when incoming email is enabled' do
context 'when both incoming email and service desk email are enabled' do
let(:gitlab_config_path) { 'spec/fixtures/config/mail_room_enabled.yml' }
let(:queues_config_path) { 'spec/fixtures/config/redis_queues_new_format_host.yml' }
let(:gitlab_redis_queues) { Gitlab::Redis::Queues.new(Rails.env) }
 
it 'contains the intended configuration' do
expect(configuration[:mailboxes].length).to eq(1)
mailbox = configuration[:mailboxes].first
expect(mailbox[:host]).to eq('imap.gmail.com')
expect(mailbox[:port]).to eq(993)
expect(mailbox[:ssl]).to eq(true)
expect(mailbox[:start_tls]).to eq(false)
expect(mailbox[:email]).to eq('gitlab-incoming@gmail.com')
expect(mailbox[:password]).to eq('[REDACTED]')
expect(mailbox[:name]).to eq('inbox')
expect(mailbox[:idle_timeout]).to eq(60)
redis_url = gitlab_redis_queues.url
sentinels = gitlab_redis_queues.sentinels
expect(mailbox[:delivery_options][:redis_url]).to be_present
expect(mailbox[:delivery_options][:redis_url]).to eq(redis_url)
expect(mailbox[:delivery_options][:sentinels]).to be_present
expect(mailbox[:delivery_options][:sentinels]).to eq(sentinels)
expect(mailbox[:arbitration_options][:redis_url]).to be_present
expect(mailbox[:arbitration_options][:redis_url]).to eq(redis_url)
expect(mailbox[:arbitration_options][:sentinels]).to be_present
expect(mailbox[:arbitration_options][:sentinels]).to eq(sentinels)
expected_mailbox = {
host: 'imap.gmail.com',
port: 993,
ssl: true,
start_tls: false,
email: 'gitlab-incoming@gmail.com',
password: '[REDACTED]',
name: 'inbox',
idle_timeout: 60
}
expected_options = {
redis_url: gitlab_redis_queues.url,
sentinels: gitlab_redis_queues.sentinels
}
expect(configuration[:mailboxes].length).to eq(2)
expect(configuration[:mailboxes]).to all(include(expected_mailbox))
expect(configuration[:mailboxes].map { |m| m[:delivery_options] }).to all(include(expected_options))
expect(configuration[:mailboxes].map { |m| m[:arbitration_options] }).to all(include(expected_options))
end
end
 
Loading
Loading
Loading
Loading
@@ -51,7 +51,7 @@ describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'P'])
 
find('.nothing-here-block')
expect(page).to have_content('Explore public groups to find projects to contribute to.')
expect(page).to have_content("This user doesn't have any personal projects")
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