Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-17 00:09:37 +00:00
parent 831b6108d2
commit d5cf5cf4f7
128 changed files with 3222 additions and 1023 deletions

View File

@ -51,9 +51,18 @@ export default {
<div class="issuable-sidebar js-issuable-update">
<sidebar-header
:sidebar-collapsed="sidebarStatus"
:project-path="projectPath"
:alert="alert"
@toggle-sidebar="$emit('toggle-sidebar')"
@alert-error="$emit('alert-error', $event)"
/>
<sidebar-todo
v-if="sidebarStatus"
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarStatus"
@alert-error="$emit('alert-error', $event)"
/>
<sidebar-todo v-if="sidebarStatus" :sidebar-collapsed="sidebarStatus" />
<sidebar-status
:project-path="projectPath"
:alert="alert"

View File

@ -167,7 +167,7 @@ export default {
if (errors[0]) {
this.$emit(
'alert-sidebar-error',
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_ASSIGNEES_GRAPHQL_ERROR} ${errors[0]}.`,
);
}

View File

@ -8,6 +8,14 @@ export default {
SidebarTodo,
},
props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: {
type: Boolean,
required: true,
@ -17,18 +25,17 @@ export default {
</script>
<template>
<div class="block d-flex justify-content-between">
<div class="block gl-display-flex gl-justify-content-space-between">
<span class="issuable-header-text hide-collapsed">
{{ __('Quick actions') }}
{{ __('To Do') }}
</span>
<toggle-sidebar
:collapsed="sidebarCollapsed"
css-classes="ml-auto"
@toggle="$emit('toggle-sidebar')"
<sidebar-todo
v-if="!sidebarCollapsed"
:project-path="projectPath"
:alert="alert"
:sidebar-collapsed="sidebarCollapsed"
@alert-error="$emit('alert-error', $event)"
/>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template v-if="false">
<sidebar-todo v-if="!sidebarCollapsed" :sidebar-collapsed="sidebarCollapsed" />
</template>
<toggle-sidebar :collapsed="sidebarCollapsed" @toggle="$emit('toggle-sidebar')" />
</div>
</template>

View File

@ -1,29 +1,123 @@
<script>
import { s__ } from '~/locale';
import Todo from '~/sidebar/components/todo_toggle/todo.vue';
import axios from '~/lib/utils/axios_utils';
import createAlertTodo from '../../graphql/mutations/alert_todo_create.graphql';
export default {
i18n: {
UPDATE_ALERT_TODO_ERROR: s__(
'AlertManagement|There was an error while updating the To Do of the alert.',
),
},
components: {
Todo,
},
props: {
alert: {
type: Object,
required: true,
},
projectPath: {
type: String,
required: true,
},
sidebarCollapsed: {
type: Boolean,
required: true,
},
},
data() {
return {
isUpdating: false,
isTodo: false,
todo: '',
};
},
computed: {
alertID() {
return parseInt(this.alert.iid, 10);
},
},
methods: {
updateToDoCount(add) {
const oldCount = parseInt(document.querySelector('.todos-count').innerText, 10);
const count = add ? oldCount + 1 : oldCount - 1;
const headerTodoEvent = new CustomEvent('todo:toggle', {
detail: {
count,
},
});
return document.dispatchEvent(headerTodoEvent);
},
toggleTodo() {
if (this.todo) {
return this.markAsDone();
}
this.isUpdating = true;
return this.$apollo
.mutate({
mutation: createAlertTodo,
variables: {
iid: this.alert.iid,
projectPath: this.projectPath,
},
})
.then(({ data: { alertTodoCreate: { todo = {}, errors = [] } } = {} } = {}) => {
if (errors[0]) {
return this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${errors[0]}.`,
);
}
this.todo = todo.id;
return this.updateToDoCount(true);
})
.catch(() => {
this.$emit(
'alert-error',
`${this.$options.i18n.UPDATE_ALERT_TODO_ERROR} ${s__(
'AlertManagement|Please try again.',
)}`,
);
})
.finally(() => {
this.isUpdating = false;
});
},
markAsDone() {
this.isUpdating = true;
return axios
.delete(`/dashboard/todos/${this.todo.split('/').pop()}`)
.then(() => {
this.todo = '';
return this.updateToDoCount(false);
})
.catch(() => {
this.$emit('alert-error', this.$options.i18n.UPDATE_ALERT_TODO_ERROR);
})
.finally(() => {
this.isUpdating = false;
});
},
},
};
</script>
<!-- TODO: Implement after or as part of: https://gitlab.com/gitlab-org/gitlab/-/issues/215946 -->
<template>
<div v-if="false" :class="{ 'block todo': sidebarCollapsed }">
<div :class="{ 'block todo': sidebarCollapsed, 'gl-ml-auto': !sidebarCollapsed }">
<todo
data-testid="alert-todo-button"
:collapsed="sidebarCollapsed"
:issuable-id="1"
:is-todo="false"
:is-action-active="false"
:issuable-id="alertID"
:is-todo="todo !== ''"
:is-action-active="isUpdating"
issuable-type="alert"
@toggleTodo="() => {}"
@toggleTodo="toggleTodo"
/>
</div>
</template>

View File

@ -0,0 +1,11 @@
mutation($projectPath: ID!, $iid: String!) {
alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) {
errors
alert {
iid
}
todo {
id
}
}
}

View File

@ -16,10 +16,11 @@ import Tracking from '~/tracking';
*/
export default function initTodoToggle() {
$(document).on('todo:toggle', (e, count) => {
const updatedCount = count || e?.detail?.count || 0;
const $todoPendingCount = $('.todos-count');
$todoPendingCount.text(highCountTrim(count));
$todoPendingCount.toggleClass('hidden', count === 0);
$todoPendingCount.text(highCountTrim(updatedCount));
$todoPendingCount.toggleClass('hidden', updatedCount === 0);
});
}

View File

@ -5,6 +5,11 @@ module NotesActions
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
# last_fetched_at is an integer number of microseconds, which is the same
# precision as PostgreSQL "timestamp" fields. It's important for them to have
# identical precision for accurate pagination
MICROSECOND = 1_000_000
included do
before_action :set_polling_interval_header, only: [:index]
before_action :require_noteable!, only: [:index, :create]
@ -13,30 +18,20 @@ module NotesActions
end
def index
notes_json = { notes: [], last_fetched_at: Time.current.to_i }
notes = notes_finder
.execute
.inc_relations_for_view
if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
notes =
ResourceEvents::MergeIntoNotesService
.new(noteable, current_user, last_fetched_at: last_fetched_at)
.execute(notes)
end
notes, meta = gather_notes
notes = prepare_notes_for_rendering(notes)
notes = notes.select { |n| n.readable_by?(current_user) }
notes_json[:notes] =
notes =
if use_note_serializer?
note_serializer.represent(notes)
else
notes.map { |note| note_json(note) }
end
render json: notes_json
# We know there's more data, so tell the frontend to poll again after 1ms
set_polling_interval_header(interval: 1) if meta[:more]
render json: meta.merge(notes: notes)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@ -101,6 +96,48 @@ module NotesActions
private
# Lower bound (last_fetched_at as specified in the request) is already set in
# the finder. Here, we select between returning all notes since then, or a
# page's worth of notes.
def gather_notes
if Feature.enabled?(:paginated_notes, project)
gather_some_notes
else
gather_all_notes
end
end
def gather_all_notes
now = Time.current
notes = merge_resource_events(notes_finder.execute.inc_relations_for_view)
[notes, { last_fetched_at: (now.to_i * MICROSECOND) + now.usec }]
end
def gather_some_notes
paginator = Gitlab::UpdatedNotesPaginator.new(
notes_finder.execute.inc_relations_for_view,
last_fetched_at: last_fetched_at
)
notes = paginator.notes
# Fetch all the synthetic notes in the same time range as the real notes.
# Although we don't limit the number, their text is under our control so
# should be fairly cheap to process.
notes = merge_resource_events(notes, fetch_until: paginator.next_fetched_at)
[notes, paginator.metadata]
end
def merge_resource_events(notes, fetch_until: nil)
return notes if notes_filter == UserPreference::NOTES_FILTERS[:only_comments]
ResourceEvents::MergeIntoNotesService
.new(noteable, current_user, last_fetched_at: last_fetched_at, fetch_until: fetch_until)
.execute(notes)
end
def note_html(note)
render_to_string(
"shared/notes/_note",
@ -229,8 +266,8 @@ module NotesActions
params.require(:note).permit(:note, :position)
end
def set_polling_interval_header
Gitlab::PollingInterval.set_header(response, interval: 6_000)
def set_polling_interval_header(interval: 6000)
Gitlab::PollingInterval.set_header(response, interval: interval)
end
def noteable
@ -242,7 +279,14 @@ module NotesActions
end
def last_fetched_at
request.headers['X-Last-Fetched-At']
strong_memoize(:last_fetched_at) do
microseconds = request.headers['X-Last-Fetched-At'].to_i
seconds = microseconds / MICROSECOND
frac = microseconds % MICROSECOND
Time.zone.at(seconds, frac)
end
end
def notes_filter

View File

@ -30,7 +30,7 @@ class Import::BaseController < ApplicationController
end
def incompatible_repos
[]
raise NotImplementedError
end
def provider_name
@ -87,15 +87,6 @@ class Import::BaseController < ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def find_jobs(import_type)
current_user.created_projects
.with_import_state
.where(import_type: import_type)
.to_json(only: [:id], methods: [:import_status])
end
# rubocop: enable CodeReuse/ActiveRecord
# deprecated: being replaced by app/services/import/base_service.rb
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names

View File

@ -22,23 +22,8 @@ class Import::BitbucketController < Import::BaseController
redirect_to status_import_bitbucket_url
end
# rubocop: disable CodeReuse/ActiveRecord
def status
return super if Feature.enabled?(:new_import_ui)
bitbucket_client = Bitbucket::Client.new(credentials)
repos = bitbucket_client.repos(filter: sanitized_filter_param)
@repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
@already_added_projects = find_already_added_projects('bitbucket')
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.to_a.reject! { |repo| already_added_projects_names.include?(repo.full_name) }
end
# rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('bitbucket')
super
end
def realtime_changes

View File

@ -52,23 +52,8 @@ class Import::BitbucketServerController < Import::BaseController
redirect_to status_import_bitbucket_server_path
end
# rubocop: disable CodeReuse/ActiveRecord
def status
return super if Feature.enabled?(:new_import_ui)
@collection = client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param)
@repos, @incompatible_repos = @collection.partition { |repo| repo.valid? }
# Use the import URL to filter beyond what BaseService#find_already_added_projects
@already_added_projects = filter_added_projects('bitbucket_server', @repos.map(&:browse_url))
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
end
# rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('bitbucket_server')
super
end
def realtime_changes

View File

@ -50,14 +50,7 @@ class Import::FogbugzController < Import::BaseController
return redirect_to new_import_fogbugz_path
end
return super if Feature.enabled?(:new_import_ui)
@repos = client.repos
@already_added_projects = find_already_added_projects('fogbugz')
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include? repo.name }
super
end
# rubocop: enable CodeReuse/ActiveRecord
@ -65,10 +58,6 @@ class Import::FogbugzController < Import::BaseController
super
end
def jobs
render json: find_jobs('fogbugz')
end
def create
repo = client.repo(params[:repo_id])
fb_session = { uri: session[:fogbugz_uri], token: session[:fogbugz_token] }
@ -96,6 +85,11 @@ class Import::FogbugzController < Import::BaseController
end
# rubocop: enable CodeReuse/ActiveRecord
override :incompatible_repos
def incompatible_repos
[]
end
override :provider_name
def provider_name
:fogbugz

View File

@ -21,15 +21,17 @@ class Import::GiteaController < Import::GithubController
super
end
protected
override :provider_name
def provider_name
:gitea
end
private
def host_key
:"#{provider}_host_url"
end
override :provider
def provider
:gitea
:"#{provider_name}_host_url"
end
override :provider_url

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class Import::GithubController < Import::BaseController
extend ::Gitlab::Utils::Override
include ImportHelper
include ActionView::Helpers::SanitizeHelper
@ -34,18 +36,11 @@ class Import::GithubController < Import::BaseController
# Improving in https://gitlab.com/gitlab-org/gitlab-foss/issues/55585
client_repos
respond_to do |format|
format.json do
render json: { imported_projects: serialized_imported_projects,
provider_repos: serialized_provider_repos,
namespaces: serialized_namespaces }
end
format.html
end
super
end
def create
result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider)
result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider_name)
if result[:status] == :success
render json: serialized_imported_projects(result[:project])
@ -55,9 +50,37 @@ class Import::GithubController < Import::BaseController
end
def realtime_changes
Gitlab::PollingInterval.set_header(response, interval: 3_000)
super
end
render json: already_added_projects.to_json(only: [:id], methods: [:import_status])
protected
# rubocop: disable CodeReuse/ActiveRecord
override :importable_repos
def importable_repos
already_added_projects_names = already_added_projects.pluck(:import_source)
client_repos.reject { |repo| already_added_projects_names.include?(repo.full_name) }
end
# rubocop: enable CodeReuse/ActiveRecord
override :incompatible_repos
def incompatible_repos
[]
end
override :provider_name
def provider_name
:github
end
override :provider_url
def provider_url
strong_memoize(:provider_url) do
provider = Gitlab::Auth::OAuth::Provider.config_for('github')
provider&.dig('url').presence || 'https://github.com'
end
end
private
@ -74,27 +97,6 @@ class Import::GithubController < Import::BaseController
ProjectSerializer.new.represent(projects, serializer: :import, provider_url: provider_url)
end
def serialized_provider_repos
repos = client_repos.reject { |repo| already_added_project_names.include? repo.full_name }
Import::ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url)
end
def serialized_namespaces
NamespaceSerializer.new.represent(namespaces)
end
def already_added_projects
@already_added_projects ||= filtered(find_already_added_projects(provider))
end
def already_added_project_names
@already_added_projects_names ||= already_added_projects.pluck(:import_source) # rubocop:disable CodeReuse/ActiveRecord
end
def namespaces
current_user.manageable_groups_with_routes
end
def expire_etag_cache
Gitlab::EtagCaching::Store.new.tap do |store|
store.touch(realtime_changes_path)
@ -118,29 +120,29 @@ class Import::GithubController < Import::BaseController
end
def import_enabled?
__send__("#{provider}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend
__send__("#{provider_name}_import_enabled?") # rubocop:disable GitlabSecurity/PublicSend
end
def realtime_changes_path
public_send("realtime_changes_import_#{provider}_path", format: :json) # rubocop:disable GitlabSecurity/PublicSend
public_send("realtime_changes_import_#{provider_name}_path", format: :json) # rubocop:disable GitlabSecurity/PublicSend
end
def new_import_url
public_send("new_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
public_send("new_import_#{provider_name}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
def status_import_url
public_send("status_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
public_send("status_import_#{provider_name}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
def callback_import_url
public_send("users_import_#{provider}_callback_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
public_send("users_import_#{provider_name}_callback_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
def provider_unauthorized
session[access_token_key] = nil
redirect_to new_import_url,
alert: "Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account."
alert: "Access denied to your #{Gitlab::ImportSources.title(provider_name.to_s)} account."
end
def provider_rate_limit(exception)
@ -151,29 +153,16 @@ class Import::GithubController < Import::BaseController
end
def access_token_key
:"#{provider}_access_token"
:"#{provider_name}_access_token"
end
def access_params
{ github_access_token: session[access_token_key] }
end
# The following methods are overridden in subclasses
def provider
:github
end
def provider_url
strong_memoize(:provider_url) do
provider = Gitlab::Auth::OAuth::Provider.config_for('github')
provider&.dig('url').presence || 'https://github.com'
end
end
# rubocop: disable CodeReuse/ActiveRecord
def logged_in_with_provider?
current_user.identities.exists?(provider: provider)
current_user.identities.exists?(provider: provider_name)
end
# rubocop: enable CodeReuse/ActiveRecord
@ -202,12 +191,6 @@ class Import::GithubController < Import::BaseController
def filter_attribute
:name
end
def filtered(collection)
return collection unless sanitized_filter_param
collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) }
end
end
Import::GithubController.prepend_if_ee('EE::Import::GithubController')

View File

@ -16,21 +16,8 @@ class Import::GitlabController < Import::BaseController
redirect_to status_import_gitlab_url
end
# rubocop: disable CodeReuse/ActiveRecord
def status
return super if Feature.enabled?(:new_import_ui)
@repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
@already_added_projects = find_already_added_projects('gitlab')
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] }
end
# rubocop: enable CodeReuse/ActiveRecord
def jobs
render json: find_jobs('gitlab')
super
end
def create
@ -63,6 +50,11 @@ class Import::GitlabController < Import::BaseController
end
# rubocop: enable CodeReuse/ActiveRecord
override :incompatible_repos
def incompatible_repos
[]
end
override :provider_name
def provider_name
:gitlab

View File

@ -158,13 +158,16 @@ class NotesFinder
end
# Notes changed since last fetch
# Uses overlapping intervals to avoid worrying about race conditions
def since_fetch_at(notes)
return notes unless @params[:last_fetched_at]
# Default to 0 to remain compatible with old clients
last_fetched_at = Time.at(@params.fetch(:last_fetched_at, 0).to_i)
notes.updated_after(last_fetched_at - FETCH_OVERLAP)
last_fetched_at = @params.fetch(:last_fetched_at, Time.at(0))
# Use overlapping intervals to avoid worrying about race conditions
last_fetched_at -= FETCH_OVERLAP
notes.updated_after(last_fetched_at)
end
def notes_filter?

View File

@ -46,7 +46,7 @@ class UserRecentEventsFinder
SQL
# Workaround for https://github.com/rails/rails/issues/24193
ensure_design_visibility(Event.from([Arel.sql(sql)]))
Event.from([Arel.sql(sql)])
end
# rubocop: enable CodeReuse/ActiveRecord
@ -59,11 +59,4 @@ class UserRecentEventsFinder
def projects
target_user.project_interactions.to_sql
end
# TODO: remove when the :design_activity_events feature flag is removed.
def ensure_design_visibility(events)
return events if Feature.enabled?(:design_activity_events)
events.not_design
end
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Mutations
module AlertManagement
module Alerts
module Todo
class Create < Base
graphql_name 'AlertTodoCreate'
def resolve(args)
alert = authorized_find!(project_path: args[:project_path], iid: args[:iid])
result = ::AlertManagement::Alerts::Todo::CreateService.new(alert, current_user).execute
prepare_response(result)
end
private
def prepare_response(result)
{
alert: result.payload[:alert],
todo: result.payload[:todo],
errors: result.error? ? [result.message] : []
}
end
end
end
end
end
end

View File

@ -18,6 +18,11 @@ module Mutations
null: true,
description: "The alert after mutation"
field :todo,
Types::TodoType,
null: true,
description: "The todo after mutation"
field :issue,
Types::IssueType,
null: true,

View File

@ -10,6 +10,7 @@ module Types
mount_mutation Mutations::AlertManagement::CreateAlertIssue
mount_mutation Mutations::AlertManagement::UpdateAlertStatus
mount_mutation Mutations::AlertManagement::Alerts::SetAssignees
mount_mutation Mutations::AlertManagement::Alerts::Todo::Create
mount_mutation Mutations::AwardEmojis::Add
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle

View File

@ -6,6 +6,7 @@ module Types
value 'ISSUE', value: 'Issue', description: 'An Issue'
value 'MERGEREQUEST', value: 'MergeRequest', description: 'A MergeRequest'
value 'DESIGN', value: 'DesignManagement::Design', description: 'A Design'
value 'ALERT', value: 'AlertManagement::Alert', description: 'An Alert'
end
end

View File

@ -69,8 +69,6 @@ module EventsHelper
end
def designs_visible?
return false unless Feature.enabled?(:design_activity_events)
if @project
design_activity_enabled?(@project)
elsif @group

View File

@ -92,6 +92,13 @@ module Emails
mail_answer_thread(@merge_request, merge_request_thread_options(resolved_by_user_id, recipient_id, reason))
end
def merge_when_pipeline_succeeds_email(recipient_id, merge_request_id, mwps_set_by_user_id, reason = nil)
setup_merge_request_mail(merge_request_id, recipient_id)
@mwps_set_by = ::User.find(mwps_set_by_user_id)
mail_answer_thread(@merge_request, merge_request_thread_options(mwps_set_by_user_id, recipient_id, reason))
end
private
def setup_merge_request_mail(merge_request_id, recipient_id, present: false)

View File

@ -177,6 +177,10 @@ class NotifyPreview < ActionMailer::Preview
Notify.service_desk_thank_you_email(issue.id).message
end
def merge_when_pipeline_succeeds_email
Notify.merge_when_pipeline_succeeds_email(user.id, merge_request.id, user.id).message
end
private
def project

View File

@ -38,6 +38,10 @@ class ApplicationRecord < ActiveRecord::Base
false
end
def self.at_most(count)
limit(count)
end
def self.safe_find_or_create_by!(*args)
safe_find_or_create_by(*args).tap do |record|
record.validate! unless record.persisted?

View File

@ -159,7 +159,16 @@ module Clusters
if ca_pem.present?
opts[:cert_store] = OpenSSL::X509::Store.new
opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
file = Tempfile.new('cluster_ca_pem_temp')
begin
file.write(ca_pem)
file.rewind
opts[:cert_store].add_file(file.path)
ensure
file.close
file.unlink # deletes the temp file
end
end
opts

View File

@ -83,9 +83,6 @@ class Event < ApplicationRecord
scope :for_wiki_page, -> { where(target_type: 'WikiPage::Meta') }
scope :for_design, -> { where(target_type: 'DesignManagement::Design') }
# Needed to implement feature flag: can be removed when feature flag is removed
scope :not_design, -> { where('target_type IS NULL or target_type <> ?', 'DesignManagement::Design') }
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association
# is not always available (depending on the query being built).

View File

@ -33,23 +33,16 @@ class EventCollection
project_events
end
relation = apply_feature_flags(relation)
relation = paginate_events(relation)
relation.with_associations.to_a
end
def all_project_events
apply_feature_flags(Event.from_union([project_events]).recent)
Event.from_union([project_events]).recent
end
private
def apply_feature_flags(events)
events = events.not_design unless ::Feature.enabled?(:design_activity_events)
events
end
def project_events
relation_with_join_lateral('project_id', projects)
end

View File

@ -123,6 +123,8 @@ class Note < ApplicationRecord
scope :common, -> { where(noteable_type: ["", nil]) }
scope :fresh, -> { order(created_at: :asc, id: :asc) }
scope :updated_after, ->(time) { where('updated_at > ?', time) }
scope :with_updated_at, ->(time) { where(updated_at: time) }
scope :by_updated_at, -> { reorder(:updated_at, :id) }
scope :inc_author_project, -> { includes(:project, :author) }
scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do

View File

@ -8,6 +8,8 @@ class ProductAnalyticsEvent < ApplicationRecord
belongs_to :project
validates :event_id, :project_id, :v_collector, :v_etl, presence: true
# There is no default Rails timestamps in the table.
# collector_tstamp is a timestamp when a collector recorded an event.
scope :order_by_time, -> { order(collector_tstamp: :desc) }

View File

@ -11,6 +11,7 @@ class ResourceEvent < ApplicationRecord
belongs_to :user
scope :created_after, ->(time) { where('created_at > ?', time) }
scope :created_on_or_before, ->(time) { where('created_at <= ?', time) }
def discussion_id
strong_memoize(:discussion_id) do

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
module AlertManagement
module Alerts
module Todo
class CreateService
# @param alert [AlertManagement::Alert]
# @param current_user [User]
def initialize(alert, current_user)
@alert = alert
@current_user = current_user
end
def execute
return error_no_permissions unless allowed?
todos = TodoService.new.mark_todo(alert, current_user)
todo = todos&.first
return error_existing_todo unless todo
success(todo)
end
private
attr_reader :alert, :current_user
def allowed?
current_user&.can?(:update_alert_management_alert, alert)
end
def error(message)
ServiceResponse.error(payload: { alert: alert, todo: nil }, message: message)
end
def success(todo)
ServiceResponse.success(payload: { alert: alert, todo: todo })
end
def error_no_permissions
error(_('You have insufficient permissions to create a Todo for this alert'))
end
def error_existing_todo
error(_('You already have pending todo for this alert'))
end
end
end
end
end

View File

@ -11,7 +11,7 @@ module AutoMerge
yield if block_given?
end
# Notify the event that auto merge is enabled or merge param is updated
notify(merge_request)
AutoMergeProcessWorker.perform_async(merge_request.id)
strategy.to_sym
@ -62,6 +62,10 @@ module AutoMerge
private
# Overridden in child classes
def notify(merge_request)
end
def strategy
strong_memoize(:strategy) do
self.class.name.demodulize.remove('Service').underscore

View File

@ -34,5 +34,13 @@ module AutoMerge
merge_request.actual_head_pipeline&.active?
end
end
private
def notify(merge_request)
return unless Feature.enabled?(:mwps_notification, project)
notification_service.async.merge_when_pipeline_succeeds(merge_request, current_user) if merge_request.saved_change_to_auto_merge_enabled?
end
end
end

View File

@ -83,8 +83,6 @@ class EventCreateService
end
def save_designs(current_user, create: [], update: [])
return [] unless Feature.enabled?(:design_activity_events)
records = create.zip([:created].cycle) + update.zip([:updated].cycle)
return [] if records.empty?
@ -92,7 +90,6 @@ class EventCreateService
end
def destroy_designs(designs, current_user)
return [] unless Feature.enabled?(:design_activity_events)
return [] unless designs.present?
create_record_events(designs.zip([:destroyed].cycle), current_user)

View File

@ -582,6 +582,14 @@ class NotificationService
end
end
def merge_when_pipeline_succeeds(merge_request, current_user)
recipients = ::NotificationRecipients::BuildService.build_recipients(merge_request, current_user, action: 'merge_when_pipeline_succeeds')
recipients.each do |recipient|
mailer.merge_when_pipeline_succeeds_email(recipient.user.id, merge_request.id, current_user.id).deliver_later
end
end
protected
def new_resource_email(target, method)

View File

@ -23,11 +23,25 @@ module ResourceEvents
private
def since_fetch_at(events)
def apply_common_filters(events)
events = apply_last_fetched_at(events)
events = apply_fetch_until(events)
events
end
def apply_last_fetched_at(events)
return events unless params[:last_fetched_at].present?
last_fetched_at = Time.zone.at(params.fetch(:last_fetched_at).to_i)
events.created_after(last_fetched_at - NotesFinder::FETCH_OVERLAP)
last_fetched_at = params[:last_fetched_at] - NotesFinder::FETCH_OVERLAP
events.created_after(last_fetched_at)
end
def apply_fetch_until(events)
return events unless params[:fetch_until].present?
events.created_on_or_before(params[:fetch_until])
end
def resource_parent

View File

@ -19,7 +19,7 @@ module ResourceEvents
return [] unless resource.respond_to?(:resource_label_events)
events = resource.resource_label_events.includes(:label, user: :status) # rubocop: disable CodeReuse/ActiveRecord
events = since_fetch_at(events)
events = apply_common_filters(events)
events.group_by { |event| event.discussion_id }
end

View File

@ -19,7 +19,7 @@ module ResourceEvents
return [] unless resource.respond_to?(:resource_milestone_events)
events = resource.resource_milestone_events.includes(user: :status) # rubocop: disable CodeReuse/ActiveRecord
since_fetch_at(events)
apply_common_filters(events)
end
end
end

View File

@ -14,7 +14,7 @@ module ResourceEvents
return [] unless resource.respond_to?(:resource_state_events)
events = resource.resource_state_events.includes(user: :status) # rubocop: disable CodeReuse/ActiveRecord
since_fetch_at(events)
apply_common_filters(events)
end
end
end

View File

@ -162,9 +162,9 @@ class TodoService
create_assignment_todo(alert, current_user, [])
end
# When user marks an issue as todo
def mark_todo(issuable, current_user)
attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
# When user marks a target as todo
def mark_todo(target, current_user)
attributes = attributes_for_todo(target.project, target, current_user, Todo::MARKED)
create_todos(current_user, attributes)
end

View File

@ -144,11 +144,6 @@
"label": "Spotbugs",
"enabled" : true
},
{
"name": "tslint",
"label": "TSLint",
"enabled" : true
},
{
"name": "secrets",
"label": "Secrets",

View File

@ -5,93 +5,4 @@
%i.fa.fa-bitbucket
= _('Import projects from Bitbucket')
- if Feature.enabled?(:new_import_ui)
= render 'import/githubish_status', provider: 'bitbucket'
- else
- if @repos.any?
%p.light
= _('Select projects you want to import.')
%p
- if @incompatible_repos.any?
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all compatible projects')
= icon('spinner spin', class: 'loading-icon')
- else
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all projects')
= icon('spinner spin', class: 'loading-icon')
.position-relative.ms-no-clear.d-flex.flex-fill.float-right.append-bottom-10
= form_tag status_import_bitbucket_path, method: 'get' do
= text_field_tag :filter, @filter, class: 'form-control pr-5', placeholder: _('Filter projects'), size: 40, autofocus: true, 'aria-label': _('Search')
.position-absolute.position-top-0.d-flex.align-items-center.text-muted.position-right-0.h-100
.border-left
%button{ class: 'btn btn-transparent btn-secondary', 'aria-label': _('Search Button'), type: 'submit' }
%i{ class: 'fa fa-search', 'aria-hidden': true }
.table-responsive
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead
%tr
%th= _('From Bitbucket')
%th= _('To GitLab')
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer'
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- case project.import_status
- when 'finished'
%span
%i.fa.fa-check
= _('done')
- when 'started'
%i.fa.fa-spinner.fa-spin
= _('started')
- else
= project.human_import_status_name
- @repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
%td
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target
%fieldset.row
.input-group
.project-path.input-group-prepend
- if current_user.can_select_namespace?
- selected = params[:namespace_id] || :current_user
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
%span.input-group-prepend
.input-group-text /
= text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%td.import-actions.job-status
= button_tag class: 'btn btn-import js-add-to-import' do
= _('Import')
= icon('spinner spin', class: 'loading-icon')
- @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
%td
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target
%td.import-actions-job-status
= label_tag _('Incompatible Project'), nil, class: 'label badge-danger'
- if @incompatible_repos.any?
%p
= _("One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.")
- link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview')
- link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path)
= _("Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again.").html_safe % { link_to_git: link_to_git, link_to_import_flow: link_to_import_flow }
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
= render 'import/githubish_status', provider: 'bitbucket'

View File

@ -5,94 +5,4 @@
%i.fa.fa-bitbucket-square
= _('Import projects from Bitbucket Server')
- if Feature.enabled?(:new_import_ui)
= render 'import/githubish_status', provider: 'bitbucket_server', extra_data: { reconfigure_path: configure_import_bitbucket_server_path }
- else
- if @repos.any?
%p.light
= _('Select projects you want to import.')
.btn-group
- if @incompatible_repos.any?
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all compatible projects')
= icon('spinner spin', class: 'loading-icon')
- else
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all projects')
= icon('spinner spin', class: 'loading-icon')
.btn-group
= link_to('Reconfigure', configure_import_bitbucket_server_path, class: 'btn btn-primary', method: :post)
.input-btn-group.float-right
= form_tag status_import_bitbucket_server_path, :method => 'get' do
= text_field_tag :filter, sanitize(params[:filter]), class: 'form-control append-bottom-10', placeholder: _('Filter your projects by name'), size: 40, autoFocus: true
.table-responsive.prepend-top-10
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead
%tr
%th= _('From Bitbucket Server')
%th= _('To GitLab')
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td
= link_to project.import_source, project.import_source, target: '_blank', rel: 'noopener noreferrer'
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- case project.import_status
- when 'finished'
= icon('check', text: 'Done')
- when 'started'
= icon('spin', text: 'started')
- else
= project.human_import_status_name
- @repos.each do |repo|
%tr{ data: { id: "#{repo.project_key}/#{repo.slug}" } }
%td
= sanitize(link_to(repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'), attributes: %w(href target rel))
%td.import-target
%fieldset.row
.input-group
.project-path.input-group-prepend
- if current_user.can_select_namespace?
- selected = params[:namespace_id] || :extra_group
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: sanitize_project_name(repo.project_key), path: sanitize_project_name(repo.project_key)) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
%span.input-group-prepend
.input-group-text /
= text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, required: true
%td.import-actions.job-status
= button_tag class: 'btn btn-import js-add-to-import' do
Import
= icon('spinner spin', class: 'loading-icon')
- @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.project_key}___#{repo.slug}" }
%td
= sanitize(link_to(repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'), attributes: %w(href target rel))
%td.import-target
%td.import-actions-job-status
= label_tag 'Incompatible Project', nil, class: 'label badge-danger'
- if @incompatible_repos.any?
%p
One or more of your Bitbucket Server projects cannot be imported into GitLab
directly because they use Subversion or Mercurial for version control,
rather than Git. Please convert
= link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview'
and go through the
= link_to 'import flow', status_import_bitbucket_server_path
again.
= paginate_without_count(@collection)
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_server_path}", import_path: "#{import_bitbucket_server_path}" } }
= render 'import/githubish_status', provider: 'bitbucket_server', extra_data: { reconfigure_path: configure_import_bitbucket_server_path }

View File

@ -4,63 +4,8 @@
%i.fa.fa-bug
= _('Import projects from FogBugz')
- if Feature.enabled?(:new_import_ui)
%p.light
- link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
%hr
= render 'import/githubish_status', provider: 'fogbugz', filterable: false
- else
- if @repos.any?
%p.light
= _('Select projects you want to import.')
%p.light
- link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
%hr
%p
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all projects')
= icon("spinner spin", class: "loading-icon")
.table-responsive
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead
%tr
%th= _("From FogBugz")
%th= _("To GitLab")
%th= _("Status")
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td
= project.import_source
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- case project.import_status
- when 'finished'
%span
%i.fa.fa-check
= _("done")
- when 'started'
%i.fa.fa-spinner.fa-spin
= _("started")
- else
= project.human_import_status_name
- @repos.each do |repo|
%tr{ id: "repo_#{repo.id}" }
%td
= repo.name
%td.import-target
#{current_user.username}/#{repo.name}
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
= _("Import")
= icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
%p.light
- link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
%hr
= render 'import/githubish_status', provider: 'fogbugz', filterable: false

View File

@ -4,55 +4,4 @@
= sprite_icon('heart', size: 16, css_class: 'gl-vertical-align-middle')
= _('Import projects from GitLab.com')
- if Feature.enabled?(:new_import_ui)
= render 'import/githubish_status', provider: 'gitlab', filterable: false
- else
%p.light
= _('Select projects you want to import.')
%hr
%p
= button_tag class: "btn btn-import btn-success js-import-all" do
= _('Import all projects')
= icon("spinner spin", class: "loading-icon")
.table-responsive
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col
%thead
%tr
%th= _('From GitLab.com')
%th= _('To this GitLab instance')
%th= _('Status')
%tbody
- @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td
= link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status
- case project.import_status
- when 'finished'
%span
%i.fa.fa-check
= _('done')
- when 'started'
%i.fa.fa-spinner.fa-spin
= _('started')
- else
= project.human_import_status_name
- @repos.each do |repo|
%tr{ id: "repo_#{repo["id"]}" }
%td
= link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank", rel: 'noopener noreferrer'
%td.import-target
= import_project_target(repo['namespace']['path'], repo['name'])
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
= _('Import')
= icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
= render 'import/githubish_status', provider: 'gitlab', filterable: false

View File

@ -0,0 +1,161 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
%html{ lang: "en" }
%head
%meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }
%meta{ content: "width=device-width, initial-scale=1", name: "viewport" }
%meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }
%title= message.subject
:css
/* CLIENT-SPECIFIC STYLES */
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; }
/* iOS BLUE LINKS */
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
/* ANDROID MARGIN HACK */
body { margin:0 !important; }
div[style*="margin: 16px 0"] { margin:0 !important; }
@media only screen and (max-width: 639px) {
body, #body {
min-width: 320px !important;
}
table.wrapper {
width: 100% !important;
min-width: 320px !important;
}
table.wrapper > tbody > tr > td {
border-left: 0 !important;
border-right: 0 !important;
border-radius: 0 !important;
padding-left: 10px !important;
padding-right: 10px !important;
}
}
ul.assignees-list {
list-style: none;
padding: 0px;
display: block;
margin-top: 0px;
}
ul.assignees-list li {
display: inline-block;
padding-right: 12px;
padding-top: 8px;
}
%body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" }
%tbody
%tr.line
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }
%tr.header
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" }
%tbody
%tr.success
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" }
%img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" }
%span= _('Merge request was scheduled to merge after pipeline succeeds')
%tr.spacer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
&nbsp;
%tr.section
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.4;text-align:center;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;width:100%;" }
%tbody
%tr{ style: 'width:100%;' }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;text-align:center;" }
%img{ src: image_url('mailers/approval/icon-merge-request-gray.gif'), style: "height:18px;width:18px;margin-bottom:-4px;", alt: "Merge request icon" }
%span{ style: "font-weight: 600;color:#333333;" }= _('Merge request')
%a{ href: merge_request_url(@merge_request), style: "font-weight: 600;color:#3777b0;text-decoration:none" }= @merge_request.to_reference
%span= _('was scheduled to merge after pipeline succeeds by')
%img.avatar{ height: "24", src: avatar_icon_for_user(@mwps_set_by, 24, only_path: false), style: "border-radius:12px;margin:-7px 0 -7px 3px;", width: "24", alt: "Avatar" }
%a.muted{ href: user_url(@mwps_set_by), style: "color:#333333;text-decoration:none;" }
= @mwps_set_by.name
%tr.spacer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" }
&nbsp;
%tr.section
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" }
%table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" }= _('Project')
-# haml-lint:disable NoPlainNodes
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" }
- namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name
- namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner)
%a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" }
= namespace_name
\/
%a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" }
= @project.name
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Branch')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%span.muted{ style: "color:#333333;text-decoration:none;" }
= @merge_request.source_branch
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }= _('Author')
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" }
%table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" }
%tbody
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" }
%img.avatar{ height: "24", src: avatar_icon_for_user(@merge_request.author, 24, only_path: false), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" }
%a.muted{ href: user_url(@merge_request.author), style: "color:#333333;text-decoration:none;" }
= @merge_request.author.name
- if @merge_request.assignees.any?
%tr
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" }
= assignees_label(@merge_request, include_value: false)
%td{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; margin: 0; padding: 14px 0 0px 5px; font-size: 15px; line-height: 1.4; color: #333333; font-weight: 400; width: 75%; border-top-style: solid; border-top-color: #ededed; border-top-width: 1px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }
%ul.assignees-list{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; font-size: 15px; line-height: 1.4; padding-right: 5px; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; mso-table-lspace: 0pt; mso-table-rspace: 0pt;" }
- @merge_request.assignees.each do |assignee|
%li
%img.avatar{ alt: "Avatar", height: "24", src: avatar_icon_for_user(assignee, 24, only_path: false), style: "border-radius: 12px; max-width: 100%; height: auto; -ms-interpolation-mode: bicubic; margin: -2px 0;", width: "24" }
%a.muted{ href: user_url(assignee), style: "color: #333333; text-decoration: none; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; vertical-align: top;" }
= assignee.name
-# EE-specific start
= render 'layouts/mailer/additional_text'
-# EE-specific end
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }
%div
- manage_notifications_link = link_to(_("Manage all notifications"), profile_notifications_url, style: "color:#3777b0;text-decoration:none;")
- help_link = link_to(_("Help"), help_url, style: "color:#3777b0;text-decoration:none;")
= _("You're receiving this email because of your account on %{host}. %{manage_notifications_link} &middot; %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }

View File

@ -0,0 +1,8 @@
Merge Request #{@merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{sanitize_name(@mwps_set_by.name)}
Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
Author: #{sanitize_name(@merge_request.author_name)}
= assignees_label(@merge_request)

View File

@ -0,0 +1,5 @@
---
title: Add ability for user to manually create a todo for an alert
merge_request: 34175
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Paginate the notes incremental fetch endpoint
merge_request: 34628
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Clean up GitlabIssueTrackerService database records
merge_request: 35221
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Add API support for instance-level Kubernetes clusters
merge_request: 36001
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Enable design activity events by default
merge_request: 37107
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Merge tslint secure analyzer with eslint secure analyzer
merge_request: 36400
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fixed issue (#198424) that prevented k8s authentication with intermediate certificates
merge_request: 31254
author: Abdelrahman Mohamed
type: fixed

View File

@ -68,6 +68,15 @@ class Rack::Attack
end
end
# Product analytics feature is in experimental stage.
# At this point we want to limit amount of events registered
# per application (aid stands for application id).
throttle('throttle_product_analytics_collector', limit: 100, period: 60) do |req|
if req.product_analytics_collector_request?
req.params['aid']
end
end
throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
if req.web_request? &&
Gitlab::Throttle.settings.throttle_authenticated_web_enabled
@ -128,6 +137,10 @@ class Rack::Attack
path =~ %r{^/-/(health|liveness|readiness)}
end
def product_analytics_collector_request?
path.start_with?('/-/collector/i')
end
def should_be_skipped?
api_internal_request? || health_check_request?
end

View File

@ -1,5 +1,6 @@
require 'sidekiq/web'
require 'sidekiq/cron/web'
require 'product_analytics/collector_app'
Rails.application.routes.draw do
concern :access_requestable do
@ -176,6 +177,9 @@ Rails.application.routes.draw do
# Used by third parties to verify CI_JOB_JWT, placeholder route
# in case we decide to move away from doorkeeper-openid_connect
get 'jwks' => 'doorkeeper/openid_connect/discovery#keys'
# Product analytics collector
match '/collector/i', to: ProductAnalytics::CollectorApp.new, via: :all
end
# End of the /-/ scope.

View File

@ -24,14 +24,12 @@ namespace :import do
resource :gitlab, only: [:create], controller: :gitlab do
get :status
get :callback
get :jobs
get :realtime_changes
end
resource :bitbucket, only: [:create], controller: :bitbucket do
get :status
get :callback
get :jobs
get :realtime_changes
end
@ -39,7 +37,6 @@ namespace :import do
post :configure
get :status
get :callback
get :jobs
get :realtime_changes
end
@ -55,7 +52,6 @@ namespace :import do
resource :fogbugz, only: [:create, :new], controller: :fogbugz do
get :status
post :callback
get :jobs
get :realtime_changes
get :new_user_map, path: :user_map

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
class RemoveGitlabIssueTrackerServiceRecords < ActiveRecord::Migration[6.0]
DOWNTIME = false
BATCH_SIZE = 5000
disable_ddl_transaction!
class Service < ActiveRecord::Base
include EachBatch
self.table_name = 'services'
def self.gitlab_issue_tracker_service
where(type: 'GitlabIssueTrackerService')
end
end
def up
Service.each_batch(of: BATCH_SIZE) do |services|
services.gitlab_issue_tracker_service.delete_all
end
end
def down
# no-op
end
end

View File

@ -23812,6 +23812,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200623121135
20200623141217
20200623141544
20200623142159
20200623170000
20200623185440
20200624075411

View File

@ -137,7 +137,7 @@ We will note in the instructions below where these secrets are required.
### PostgreSQL
NOTE: **Note:**
do not store the GitLab application database and the Praefect
Do not store the GitLab application database and the Praefect
database on the same PostgreSQL server if using
[Geo](../geo/replication/index.md). The replication state is internal to each instance
of GitLab and should not be replicated.

View File

@ -129,6 +129,7 @@ The following API resources are available outside of project and group contexts
| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` |
| [Group Activity Analytics](group_activity_analytics.md) **(STARTER)** | `/analytics/group_activity/{issues_count | merge_requests_count | new_members_count }` |
| [Import repository from GitHub](import.md) | `/import/github` |
| [Instance clusters](instance_clusters.md) | `/admin/clusters` |
| [Issues](issues.md) | `/issues` (also available for groups and projects) |
| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) |
| [Keys](keys.md) | `/keys` |

View File

@ -603,6 +603,61 @@ type AlertSetAssigneesPayload {
The issue created after mutation
"""
issue: Issue
"""
The todo after mutation
"""
todo: Todo
}
"""
Autogenerated input type of AlertTodoCreate
"""
input AlertTodoCreateInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The iid of the alert to mutate
"""
iid: String!
"""
The project the alert to mutate is in
"""
projectPath: ID!
}
"""
Autogenerated return type of AlertTodoCreate
"""
type AlertTodoCreatePayload {
"""
The alert after mutation
"""
alert: AlertManagementAlert
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The issue created after mutation
"""
issue: Issue
"""
The todo after mutation
"""
todo: Todo
}
"""
@ -1575,6 +1630,11 @@ type CreateAlertIssuePayload {
The issue created after mutation
"""
issue: Issue
"""
The todo after mutation
"""
todo: Todo
}
"""
@ -8114,6 +8174,7 @@ type Mutation {
addProjectToSecurityDashboard(input: AddProjectToSecurityDashboardInput!): AddProjectToSecurityDashboardPayload
adminSidekiqQueuesDeleteJobs(input: AdminSidekiqQueuesDeleteJobsInput!): AdminSidekiqQueuesDeleteJobsPayload
alertSetAssignees(input: AlertSetAssigneesInput!): AlertSetAssigneesPayload
alertTodoCreate(input: AlertTodoCreateInput!): AlertTodoCreatePayload
awardEmojiAdd(input: AwardEmojiAddInput!): AwardEmojiAddPayload
awardEmojiRemove(input: AwardEmojiRemoveInput!): AwardEmojiRemovePayload
awardEmojiToggle(input: AwardEmojiToggleInput!): AwardEmojiTogglePayload
@ -13339,6 +13400,11 @@ enum TodoStateEnum {
}
enum TodoTargetEnum {
"""
An Alert
"""
ALERT
"""
A Commit
"""
@ -13660,6 +13726,11 @@ type UpdateAlertStatusPayload {
The issue created after mutation
"""
issue: Issue
"""
The todo after mutation
"""
todo: Todo
}
"""

View File

@ -1460,6 +1460,164 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "AlertTodoCreateInput",
"description": "Autogenerated input type of AlertTodoCreate",
"fields": null,
"inputFields": [
{
"name": "projectPath",
"description": "The project the alert to mutate is in",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "iid",
"description": "The iid of the alert to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "AlertTodoCreatePayload",
"description": "Autogenerated return type of AlertTodoCreate",
"fields": [
{
"name": "alert",
"description": "The alert after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "AlertManagementAlert",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue created after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@ -4177,6 +4335,20 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@ -22854,6 +23026,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "alertTodoCreate",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "AlertTodoCreateInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "AlertTodoCreatePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "awardEmojiAdd",
"description": null,
@ -39508,6 +39707,12 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ALERT",
"description": "An Alert",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "EPIC",
"description": "An Epic",
@ -40394,6 +40599,20 @@
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todo",
"description": "The todo after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,

View File

@ -101,6 +101,19 @@ Autogenerated return type of AlertSetAssignees
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## AlertTodoCreatePayload
Autogenerated return type of AlertTodoCreate
| Name | Type | Description |
| --- | ---- | ---------- |
| `alert` | AlertManagementAlert | The alert after mutation |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## AwardEmoji
@ -274,6 +287,7 @@ Autogenerated return type of CreateAlertIssue
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## CreateAnnotationPayload
@ -2059,6 +2073,7 @@ Autogenerated return type of UpdateAlertStatus
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue created after mutation |
| `todo` | Todo | The todo after mutation |
## UpdateContainerExpirationPolicyPayload

View File

@ -0,0 +1,293 @@
# Instance clusters API
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36001) in GitLab 13.2.
NOTE: **Note:**
User will need admin access to use these endpoints.
Use these API endpoints with your instance clusters, which enable you to use the same cluster across multiple projects. [More information](../user/instance/clusters/index.md)
## List instance clusters
Returns a list of instance clusters.
```plaintext
GET /admin/clusters
```
Example request:
```shell
curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/admin/clusters"
```
Example response:
```json
[
{
"id": 9,
"name": "cluster-1",
"created_at": "2020-07-14T18:36:10.440Z",
"domain": null,
"provider_type": "user",
"platform_type": "kubernetes",
"environment_scope": "*",
"cluster_type": "instance_type",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
},
"platform_kubernetes": {
"api_url": "https://example.com",
"namespace": null,
"authorization_type": "rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----IxMDM1MV0ZDJkZjM...-----END CERTIFICATE-----"
},
"provider_gcp": null,
"management_project": null
},
{
"id": 10,
"name": "cluster-2",
"created_at": "2020-07-14T18:39:05.383Z",
"domain": null,
"provider_type": "user",
"platform_type": "kubernetes",
"environment_scope": "staging",
"cluster_type": "instance_type",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
},
"platform_kubernetes": {
"api_url": "https://example.com",
"namespace": null,
"authorization_type": "rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----LzEtMCadtaLGxcsGAZjM...-----END CERTIFICATE-----"
},
"provider_gcp": null,
"management_project": null
}
{
"id": 11,
"name": "cluster-3",
...
}
]
```
## Get a single instance cluster
Returns a single instance cluster.
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `cluster_id` | integer | yes | The ID of the cluster |
```plaintext
GET /admin/clusters/:cluster_id
```
Example request:
```shell
curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/admin/clusters/9"
```
Example response:
```json
{
"id": 9,
"name": "cluster-1",
"created_at": "2020-07-14T18:36:10.440Z",
"domain": null,
"provider_type": "user",
"platform_type": "kubernetes",
"environment_scope": "*",
"cluster_type": "instance_type",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
},
"platform_kubernetes": {
"api_url": "https://example.com",
"namespace": null,
"authorization_type": "rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----IxMDM1MV0ZDJkZjM...-----END CERTIFICATE-----"
},
"provider_gcp": null,
"management_project": null
}
```
## Add existing instance cluster
Adds an existing Kubernetes instance cluster.
```plaintext
POST /admin/clusters/add
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `name` | string | yes | The name of the cluster |
| `domain` | string | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster |
| `environment_scope` | string | no | The associated environment to the cluster. Defaults to `*` |
| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster |
| `enabled` | boolean | no | Determines if cluster is active or not, defaults to true |
| `managed` | boolean | no | Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true |
| `platform_kubernetes_attributes[api_url]` | string | yes | The URL to access the Kubernetes API |
| `platform_kubernetes_attributes[token]` | string | yes | The token to authenticate against Kubernetes |
| `platform_kubernetes_attributes[ca_cert]` | string | no | TLS certificate. Required if API is using a self-signed TLS certificate. |
| `platform_kubernetes_attributes[namespace]` | string | no | The unique namespace related to the project |
| `platform_kubernetes_attributes[authorization_type]` | string | no | The cluster authorization type: `rbac`, `abac` or `unknown_authorization`. Defaults to `rbac`. |
Example request:
```shell
curl --header "Private-Token:<your_access_token>" "http://gitlab.example.com/api/v4/admin/clusters/add" \
-H "Accept:application/json" \
-H "Content-Type:application/json" \
-X POST --data '{"name":"cluster-3", "environment_scope":"production", "platform_kubernetes_attributes":{"api_url":"https://example.com", "token":"12345", "ca_cert":"-----BEGIN CERTIFICATE-----qpoeiXXZafCM0ZDJkZjM...-----END CERTIFICATE-----"}}'
```
Example response:
```json
{
"id": 11,
"name": "cluster-3",
"created_at": "2020-07-14T18:42:50.805Z",
"domain": null,
"provider_type": "user",
"platform_type": "kubernetes",
"environment_scope": "production",
"cluster_type": "instance_type",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "http://gitlab.example.com:3000/root"
},
"platform_kubernetes": {
"api_url": "https://example.com",
"namespace": null,
"authorization_type": "rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----qpoeiXXZafCM0ZDJkZjM...-----END CERTIFICATE-----"
},
"provider_gcp": null,
"management_project": null
}
```
## Edit instance cluster
Updates an existing instance cluster.
```shell
PUT /admin/clusters/:cluster_id
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `cluster_id` | integer | yes | The ID of the cluster |
| `name` | string | no | The name of the cluster |
| `domain` | string | no | The [base domain](../user/project/clusters/index.md#base-domain) of the cluster |
| `environment_scope` | string | no | The associated environment to the cluster |
| `management_project_id` | integer | no | The ID of the [management project](../user/clusters/management_project.md) for the cluster |
| `enabled` | boolean | no | Determines if cluster is active or not, defaults to true |
| `platform_kubernetes_attributes[api_url]` | string | no | The URL to access the Kubernetes API |
| `platform_kubernetes_attributes[token]` | string | no | The token to authenticate against Kubernetes |
| `platform_kubernetes_attributes[ca_cert]` | string | no | TLS certificate. Required if API is using a self-signed TLS certificate. |
| `platform_kubernetes_attributes[namespace]` | string | no | The unique namespace related to the project |
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
through the [Add existing Kubernetes cluster](../user/project/clusters/add_remove_clusters.md#add-existing-cluster) option or
through the [Add existing instance cluster](#add-existing-instance-cluster) endpoint.
Example request:
```shell
curl --header "Private-Token: <your_access_token>" "http://gitlab.example.com/api/v4/admin/clusters/9" \
-H "Content-Type:application/json" \
-X PUT --data '{"name":"update-cluster-name", "platform_kubernetes_attributes":{"api_url":"https://new-example.com","token":"new-token"}}'
```
Example response:
```json
{
"id": 9,
"name": "update-cluster-name",
"created_at": "2020-07-14T18:36:10.440Z",
"domain": null,
"provider_type": "user",
"platform_type": "kubernetes",
"environment_scope": "*",
"cluster_type": "instance_type",
"user": {
"id": 1,
"name": "Administrator",
"username": "root",
"state": "active",
"avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
"web_url": "https://gitlab.example.com/root"
},
"platform_kubernetes": {
"api_url": "https://new-example.com",
"namespace": null,
"authorization_type": "rbac",
"ca_cert":"-----BEGIN CERTIFICATE-----IxMDM1MV0ZDJkZjM...-----END CERTIFICATE-----"
},
"provider_gcp": null,
"management_project": null,
"project": null
}
```
## Delete instance cluster
Deletes an existing instance cluster.
```plaintext
DELETE /admin/clusters/:cluster_id
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `cluster_id` | integer | yes | The ID of the cluster |
Example request:
```shell
curl --request DELETE --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/admin/clusters/11"
```

View File

@ -6,6 +6,8 @@ type: reference
---
# Configuring GitLab Runners
<!-- This topic contains several commented-out sections that were accidentally added in 13.2.-->
<!-- The commented-out sections will be added back in 13.3.-->
In GitLab CI/CD, Runners run the code defined in [`.gitlab-ci.yml`](../yaml/README.md).
A GitLab Runner is a lightweight, highly-scalable agent that picks up a CI job through
@ -37,9 +39,11 @@ multiple projects.
If you are using a self-managed instance of GitLab:
- Your administrator can install and register shared Runners by going to your project's
**Settings > CI / CD**, expanding the **Runners** section, and clicking **Show Runner installation instructions**.
These instructions are also available [here](https://docs.gitlab.com/runner/install/index.html).
- Your administrator can install and register shared Runners by viewing the instructions
[here](https://docs.gitlab.com/runner/install/index.html).
<!-- going to your project's
<!-- **Settings > CI / CD**, expanding the **Runners** section, and clicking **Show Runner installation instructions**.-->
<!-- These instructions are also available [here](https://docs.gitlab.com/runner/install/index.html).-->
- The administrator can also configure a maximum number of shared Runner [pipeline minutes for
each group](../../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota-starter-only).
@ -119,22 +123,21 @@ To enable shared Runners:
#### Disable shared Runners
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23123) for groups in GitLab 13.2.
You can disable shared Runners for individual projects or for groups.
You must have Owner permissions for the project or group.
You can disable shared Runners for individual projects<!-- or for groups-->.
You must have Owner permissions for the project<!-- or group-->.
To disable shared Runners for a project:
1. Go to the project's **{settings}** **Settings > CI/CD** and expand the **Runners** section.
1. In the **Shared Runners** area, click **Disable shared Runners**.
To disable shared Runners for a group:
<!--To disable shared Runners for a group:
1. Go to the group's **{settings}** **Settings > CI/CD** and expand the **Runners** section.
1. In the **Shared Runners** area, click **Disable shared Runners globally**.
1. Optionally, to allow shared Runners to be enabled for individual projects or subgroups,
click **Allow projects/subgroups to override the global setting**.
-->
### Group Runners
@ -156,9 +159,9 @@ To create a group Runner:
1. Note the URL and token.
1. [Register the Runner](https://docs.gitlab.com/runner/register/).
#### View and manage group Runners
<!-- #### View and manage group Runners
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37366/) in GitLab 13.2.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37366/) in GitLab 13.3.
You can view and manage all Runners for a group, its subgroups, and projects.
You can do this for your self-managed GitLab instance or for GitLab.com.
@ -180,7 +183,7 @@ You must have [Owner permissions](../../user/permissions.md#group-members-permis
| Tags | Tags associated with the Runner |
| Last contact | Timestamp indicating when the GitLab instance last contacted the Runner |
From this page, you can edit, pause, and remove Runners from the group, its subgroups, and projects.
From this page, you can edit, pause, and remove Runners from the group, its subgroups, and projects. -->
#### Pause or remove a group Runner
@ -190,9 +193,9 @@ You must have [Owner permissions](../../user/permissions.md#group-members-permis
1. Go to the group you want to remove or pause the Runner for.
1. Go to **{settings}** **Settings > CI/CD** and expand the **Runners** section.
1. Click **Pause** or **Remove Runner**.
- If you pause a group Runner that is used by multiple projects, the Runner pauses for all projects.
- From the group view, you cannot remove a Runner that is assigned to more than one project.
You must remove it from each project first.
<!-- - If you pause a group Runner that is used by multiple projects, the Runner pauses for all projects. -->
<!-- - From the group view, you cannot remove a Runner that is assigned to more than one project. -->
<!-- You must remove it from each project first. -->
1. On the confirmation dialog, click **OK**.
### Specific Runners

View File

@ -19,6 +19,10 @@ A database review is required for:
generally up to the author of a merge request to decide whether or
not complex queries are being introduced and if they require a
database review.
- Changes in usage data metrics that use `count` and `distinct_count`.
These metrics could have complex queries over large tables.
See the [Telemetry Guide](telemetry/usage_ping.md#implementing-usage-ping)
for implementation details.
A database reviewer is expected to look out for obviously complex
queries in the change and review those closer. If the author does not

View File

@ -1,7 +1,14 @@
# GitLab Developers Guide to Working with Gitaly
---
stage: Create
group: Gitaly
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
type: reference
---
[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab CE/EE,
Workhorse and GitLab-Shell.
# Gitaly developers guide
[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab Rails,
Workhorse and GitLab Shell.
## Deep Dive

View File

@ -546,7 +546,8 @@ gitlab=# \q
#### Set up Gitaly
CAUTION: **Caution:**
In this architecture, having a single Gitaly server creates a single point of failure. This limitation will be removed once [Gitaly Cluster](https://gitlab.com/groups/gitlab-org/-/epics/1489) is released.
In this architecture, having a single Gitaly server creates a single point of failure. Use
[Gitaly Cluster](../../administration/gitaly/praefect.md) to remove this limitation.
Gitaly is a service that provides high-level RPC access to Git repositories.
It should be enabled and configured on a separate EC2 instance in one of the

View File

@ -21,7 +21,7 @@ state of each feature. If a job with the expected security report artifact exist
the feature is considered configured.
NOTE: **Note:**
if the latest pipeline used [Auto DevOps](../../../topics/autodevops/index.md),
If the latest pipeline used [Auto DevOps](../../../topics/autodevops/index.md),
all security features will be configured by default.
## Limitations

View File

@ -32,7 +32,6 @@ SAST supports the following official analyzers:
- [`security-code-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) (Security Code Scan (.NET))
- [`sobelow`](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) (Sobelow (Elixir Phoenix))
- [`spotbugs`](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) (SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT))
- [`tslint`](https://gitlab.com/gitlab-org/security-products/analyzers/tslint) (TSLint (TypeScript))
The analyzers are published as Docker images that SAST will use to launch
dedicated containers for each analysis.
@ -145,24 +144,24 @@ The [Security Scanner Integration](../../../development/integrations/secure.md)
## Analyzers Data
| Property \ Tool | Apex | Bandit | Brakeman | ESLint security | SpotBugs | Flawfinder | Gosec | Kubesec Scanner | NodeJsScan | PHP CS Security Audit | Security code Scan (.NET) | Sobelow | TSLint Security |
| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :----------------: | :-------------: |
| Severity | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | ✓ |
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Description | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ |
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | ✓ |
| End line | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | ✓ |
| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| External ID (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Affected item (e.g. class or package) | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
| Source code extract | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ |
| Property / Tool | Apex | Bandit | Brakeman | ESLint security | SpotBugs | Flawfinder | Gosec | Kubesec Scanner | NodeJsScan | PHP CS Security Audit | Security code Scan (.NET) | Sobelow |
| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :----------------: |
| Severity | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 |
| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Description | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ |
| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ |
| End line | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 |
| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| External ID (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Affected item (e.g. class or package) | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | ✓ |
| Source code extract | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ |
- ✓ => we have that data
- ⚠ => we have that data but it's partially reliable, or we need to extract it from unstructured content

View File

@ -71,15 +71,15 @@ The following table shows which languages, package managers and frameworks are s
| Language (package managers) / framework | Scan tool | Introduced in GitLab Version |
|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------|
| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0 |
| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0 |
| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11.9 |
| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1 |
| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7 |
| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10 |
| Go | [Gosec](https://github.com/securego/gosec) | 10.7 |
| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT) |
| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1 |
| .NET Core | [Security Code Scan](https://security-code-scan.github.io) | 11.0 |
| .NET Framework | [Security Code Scan](https://security-code-scan.github.io) | 13.0 |
| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11.9 |
| Apex (Salesforce) | [PMD](https://pmd.github.io/pmd/index.html) | 12.1 |
| C/C++ | [Flawfinder](https://github.com/david-a-wheeler/flawfinder) | 10.7 |
| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10 |
| Go | [Gosec](https://github.com/securego/gosec) | 10.7 |
| Groovy ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.3 (Gradle) & 11.9 (Ant, Maven, SBT) |
| Helm Charts | [Kubesec](https://github.com/controlplaneio/kubesec) | 13.1 |
| Java ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 10.6 (Maven), 10.8 (Gradle) & 11.9 (Ant, SBT) |
| JavaScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.8, moved to [GitLab Core](https://about.gitlab.com/pricing/) in 13.2 |
| Kubernetes manifests | [Kubesec](https://github.com/controlplaneio/kubesec) | 12.6 |
@ -89,7 +89,7 @@ The following table shows which languages, package managers and frameworks are s
| React | [ESLint react plugin](https://github.com/yannickcr/eslint-plugin-react) | 12.5 |
| Ruby on Rails | [brakeman](https://brakemanscanner.org) | 10.3, moved to [GitLab Core](https://about.gitlab.com/pricing/) in 13.1 |
| Scala ([Ant](https://ant.apache.org/), [Gradle](https://gradle.org/), [Maven](https://maven.apache.org/) and [SBT](https://www.scala-sbt.org/)) | [SpotBugs](https://spotbugs.github.io/) with the [find-sec-bugs](https://find-sec-bugs.github.io/) plugin | 11.0 (SBT) & 11.9 (Ant, Gradle, Maven) |
| TypeScript | [`tslint-config-security`](https://github.com/webschik/tslint-config-security/) | 11.9 |
| TypeScript | [ESLint security plugin](https://github.com/nodesecurity/eslint-plugin-security) | 11.9, merged with ESLint in 13.2 |
NOTE: **Note:**
The Java analyzers can also be used for variants like the
@ -529,7 +529,6 @@ registry.gitlab.com/gitlab-org/security-products/analyzers/secrets:2
registry.gitlab.com/gitlab-org/security-products/analyzers/security-code-scan:2
registry.gitlab.com/gitlab-org/security-products/analyzers/sobelow:2
registry.gitlab.com/gitlab-org/security-products/analyzers/spotbugs:2
registry.gitlab.com/gitlab-org/security-products/analyzers/tslint:2
```
The process for importing Docker images into a local offline Docker registry depends on

View File

@ -1495,6 +1495,12 @@ NOTE: **Note:**
Support for installing the AppArmor managed application is provided by the GitLab Container Security group.
If you run into unknown issues, please [open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new) and ping at least 2 people from the [Container Security group](https://about.gitlab.com/handbook/product/product-categories/#container-security-group).
## Browse applications logs
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36769) in GitLab 13.2.
Logs produced by pods running **GitLab Managed Apps** can be browsed using [**Log Explorer**](../project/clusters/kubernetes_pod_logs.md).
## Upgrading applications
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/24789) in GitLab 11.8.

View File

@ -117,19 +117,21 @@ and the CI YAML file:
```
1. In the `.gitlab-ci.yaml` file, define some environment variables to ease
development. In this example, `TF_STATE` is the name of the Terraform state
(projects may have multiple states), `TF_ADDRESS` is the URL to the state on
the GitLab instance where this pipeline runs, and `TF_ROOT` is the directory
where the Terraform commands must be executed:
development. In this example, `TF_ROOT` is the directory where the Terraform
commands must be executed, `TF_ADDRESS` is the URL to the state on the GitLab
instance where this pipeline runs, and the final path segment in `TF_ADDRESS`
is the name of the Terraform state. Projects may have multiple states, and
this name is arbitrary, so in this example we will set it to the name of the
project, and we will ensure that the `.terraform` directory is cached between
jobs in the pipeline using a cache key based on the state name:
```yaml
variables:
TF_STATE: ${CI_PROJECT_NAME}
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE}
TF_ROOT: ${CI_PROJECT_DIR}/environments/cloudflare/production
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}
cache:
key: ${TF_STATE}
key: ${CI_PROJECT_NAME}
paths:
- ${TF_ROOT}/.terraform
```
@ -273,12 +275,11 @@ can configure this manually as follows:
image: registry.gitlab.com/gitlab-org/terraform-images/stable:latest
variables:
TF_STATE: ${CI_PROJECT_NAME}
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE}
TF_ROOT: ${CI_PROJECT_DIR}/environments/cloudflare/production
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME}
cache:
key: ${TF_STATE}
key: ${CI_PROJECT_NAME}
paths:
- ${TF_ROOT}/.terraform

View File

@ -1,6 +1,6 @@
---
stage: Defend
group: container_security
group: Container Security
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -14,8 +14,6 @@ want to configure.
![Integrations list](img/project_services.png)
Below, you will find a list of the currently supported ones accompanied with comprehensive documentation.
## Integrations listing
Click on the service links to see further configuration instructions and details.
@ -69,16 +67,16 @@ supported by `push_hooks` and `tag_push_hooks` events won't be executed.
The number of branches or tags supported can be changed via
[`push_event_hooks_limit` application setting](../../../api/settings.md#list-of-settings-that-can-be-accessed-via-api-calls).
## Services templates
## Service templates
Services templates is a way to set some predefined values in the Service of
your liking which will then be pre-filled on each project's Service.
Service templates are a way to set predefined values for an integration across
all new projects on the instance.
Read more about [Services templates in this document](services_templates.md).
Read more about [Service templates in this document](services_templates.md).
## Troubleshooting integrations
Some integrations use service hooks for integration with external applications. To confirm which ones use service hooks, see the [integrations listing](#integrations-listing). GitLab stores details of service hook requests made within the last 2 days. To view details of the requests, go to the service's configuration page.
Some integrations use service hooks for integration with external applications. To confirm which ones use service hooks, see the [integrations listing](#integrations-listing) above. GitLab stores details of service hook requests made within the last 2 days. To view details of the requests, go to that integration's configuration page.
The **Recent Deliveries** section lists the details of each request made within the last 2 days:
@ -89,17 +87,17 @@ The **Recent Deliveries** section lists the details of each request made within
- Relative time in which the request was made
To view more information about the request's execution, click the respective **View details** link.
On the details page, you can see the data sent by GitLab (request headers and body) and the data received by GitLab (response headers and body).
On the details page, you can see the request headers and body sent and received by GitLab.
From this page, you can repeat delivery with the same data by clicking **Resend Request**.
To repeat a delivery using the same data, click **Resend Request**.
![Recent deliveries](img/webhook_logs.png)
### Uninitialized repositories
Some integrations fail with an error `Test Failed. Save Anyway` when you attempt to set them up on
uninitialized repositories. This is because the default service test uses push data to build the
payload for the test request, and it fails, because there are no push events for the project.
uninitialized repositories. Some integrations use push data to build the test payload,
and this error occurs when no push events exist in the project yet.
To resolve this error, initialize the repository by pushing a test file to the project and set up
the integration again.

View File

@ -1,28 +1,25 @@
# Services templates
# Service templates
A GitLab administrator can add a service template that sets a default for each
project. After a service template is enabled, it will be applied to **all**
projects that don't have it already enabled and its details will be pre-filled
on the project's Service page. By disabling the template, it will be disabled
for new projects only.
Using a service template, GitLab administrators can provide default values for configuring integrations at the project level.
When you enable a service template, the defaults are applied to **all** projects that do not
already have the integration enabled or do not otherwise have custom values saved.
The values are pre-filled on each project's configuration page for the applicable integration.
If you disable the template, these values no longer appear as defaults, while
any values already saved for an integration remain unchanged.
## Enable a service template
Navigate to the **Admin Area > Service Templates** and choose the service
template you wish to create.
## Services for external issue trackers
## Service for external issue trackers
In the image below you can see how a service template for Redmine would look
like.
The following image shows an example service template for Redmine.
![Redmine service template](img/services_templates_redmine_example.png)
---
For each project, you will still need to configure the issue tracking
URLs by replacing `:issues_tracker_id` in the above screenshot with the ID used
by your external issue tracker. Prior to GitLab v7.8, this ID was configured in
the project settings, and GitLab would automatically update the URL configured
in `gitlab.yml`. This behavior is now deprecated and all issue tracker URLs
must be configured directly within the project's **Integrations** settings.
by your external issue tracker.

View File

@ -60,6 +60,15 @@ and [PDFs](https://gitlab.com/gitlab-org/gitlab/-/issues/32811) is planned for a
- Only the latest version of the designs can be deleted.
- Deleted designs cannot be recovered but you can see them on previous designs versions.
## GitLab-Figma plugin
> [Introduced](https://gitlab.com/gitlab-org/gitlab-figma-plugin/-/issues/2) in GitLab 13.2.
Connect your design environment with your source code management in a seamless workflow. The GitLab-Figma plugin makes it quick and easy to collaborate in GitLab by bringing the work of product designers directly from Figma to GitLab Issues as uploaded Designs.
To use the plugin, install it from the [Figma Directory](https://www.figma.com/community/plugin/860845891704482356)
and connect to GitLab through a personal access token. The details are explained in the [plugin documentation](https://gitlab.com/gitlab-org/gitlab-figma-plugin/-/wikis/home).
## The Design Management section
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223193) in GitLab 13.2, Designs are displayed directly on the issue description rather than on a separate tab.
@ -250,32 +259,10 @@ Feature.disable(:design_management_reference_filter_gfm_pipeline)
## Design activity records
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33051) in GitLab 13.1
> - It's deployed behind a feature flag, disabled by default.
> - It's enabled on GitLab.com.
> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-design-events-core-only). **(CORE ONLY)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33051) in GitLab 13.1.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/225205) in GitLab 13.2.
User activity events on designs (creation, deletion, and updates) are tracked by GitLab and
displayed on the [user profile](../../profile/index.md#user-profile),
[group](../../group/index.md#view-group-activity),
and [project](../index.md#project-activity) activity pages.
### Enable or disable Design Events **(CORE ONLY)**
User activity for designs is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session)
can enable it for your instance. You're welcome to test it, but use it at your
own risk.
To enable it:
```ruby
Feature.enable(:design_activity_events)
```
To disable it:
```ruby
Feature.disable(:design_activity_events)
```

View File

@ -0,0 +1,134 @@
# frozen_string_literal: true
module API
module Admin
class InstanceClusters < Grape::API::Instance
include PaginationParams
before do
authenticated_as_admin!
end
namespace 'admin' do
desc "Get list of all instance clusters" do
detail "This feature was introduced in GitLab 13.2."
end
get '/clusters' do
authorize! :read_cluster, clusterable_instance
present paginate(clusters_for_current_user), with: Entities::Cluster
end
desc "Get a single instance cluster" do
detail "This feature was introduced in GitLab 13.2."
end
params do
requires :cluster_id, type: Integer, desc: "The cluster ID"
end
get '/clusters/:cluster_id' do
authorize! :read_cluster, cluster
present cluster, with: Entities::Cluster
end
desc "Add an instance cluster" do
detail "This feature was introduced in GitLab 13.2."
end
params do
requires :name, type: String, desc: 'Cluster name'
optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
optional :environment_scope, default: '*', type: String, desc: 'The associated environment to the cluster'
optional :domain, type: String, desc: 'Cluster base domain'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true'
requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
requires :api_url, type: String, allow_blank: false, desc: 'URL to access the Kubernetes API'
requires :token, type: String, desc: 'Token to authenticate against Kubernetes'
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
optional :namespace, type: String, desc: 'Unique namespace related to Project'
optional :authorization_type, type: String, values: ::Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
end
end
post '/clusters/add' do
authorize! :add_cluster, clusterable_instance
user_cluster = ::Clusters::CreateService
.new(current_user, create_cluster_user_params)
.execute
if user_cluster.persisted?
present user_cluster, with: Entities::Cluster
else
render_validation_error!(user_cluster)
end
end
desc "Update an instance cluster" do
detail "This feature was introduced in GitLab 13.2."
end
params do
requires :cluster_id, type: Integer, desc: 'The cluster ID'
optional :name, type: String, desc: 'Cluster name'
optional :enabled, type: Boolean, desc: 'Enable or disable Gitlab\'s connection to your Kubernetes cluster'
optional :environment_scope, type: String, desc: 'The associated environment to the cluster'
optional :domain, type: String, desc: 'Cluster base domain'
optional :management_project_id, type: Integer, desc: 'The ID of the management project'
optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
optional :namespace, type: String, desc: 'Unique namespace related to Project'
end
end
put '/clusters/:cluster_id' do
authorize! :update_cluster, cluster
update_service = ::Clusters::UpdateService.new(current_user, update_cluster_params)
if update_service.execute(cluster)
present cluster, with: Entities::ClusterProject
else
render_validation_error!(cluster)
end
end
desc "Remove a cluster" do
detail "This feature was introduced in GitLab 13.2."
end
params do
requires :cluster_id, type: Integer, desc: "The cluster ID"
end
delete '/clusters/:cluster_id' do
authorize! :admin_cluster, cluster
destroy_conditionally!(cluster)
end
end
helpers do
def clusterable_instance
Clusters::Instance.new
end
def clusters_for_current_user
@clusters_for_current_user ||= ClustersFinder.new(clusterable_instance, current_user, :all).execute
end
def cluster
@cluster ||= clusters_for_current_user.find(params[:cluster_id])
end
def create_cluster_user_params
declared_params.merge({
provider_type: :user,
platform_type: :kubernetes,
clusterable: clusterable_instance
})
end
def update_cluster_params
declared_params(include_missing: false).without(:cluster_id)
end
end
end
end
end

View File

@ -125,6 +125,7 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::Admin::Ci::Variables
mount ::API::Admin::InstanceClusters
mount ::API::Admin::Sidekiq
mount ::API::Appearance
mount ::API::Applications

View File

@ -26,8 +26,6 @@ class EventFilter
# rubocop: disable CodeReuse/ActiveRecord
def apply_filter(events)
events = apply_feature_flags(events)
case filter
when PUSH
events.pushed_action
@ -51,29 +49,17 @@ class EventFilter
private
def apply_feature_flags(events)
events = events.not_design unless can_view_design_activity?
events
end
def wiki_events(events)
events.for_wiki_page
end
def design_events(events)
return events.for_design if can_view_design_activity?
events
events.for_design
end
def filters
[ALL, PUSH, MERGED, ISSUE, COMMENTS, TEAM, WIKI, DESIGNS]
end
def can_view_design_activity?
Feature.enabled?(:design_activity_events)
end
end
EventFilter.prepend_if_ee('EE::EventFilter')

View File

@ -9,7 +9,7 @@ variables:
# (SAST, Dependency Scanning, ...)
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, tslint, secrets, sobelow, pmd-apex, kubesec"
SAST_DEFAULT_ANALYZERS: "bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, secrets, sobelow, pmd-apex, kubesec"
SAST_EXCLUDED_PATHS: "spec, test, tests, tmp"
SAST_ANALYZER_IMAGE_TAG: 2
SAST_DISABLE_DIND: "true"
@ -95,6 +95,8 @@ eslint-sast:
- '**/*.html'
- '**/*.js'
- '**/*.jsx'
- '**/*.ts'
- '**/*.tsx'
flawfinder-sast:
extends: .sast-analyzer
@ -226,16 +228,3 @@ spotbugs-sast:
- '**/*.groovy'
- '**/*.java'
- '**/*.scala'
tslint-sast:
extends: .sast-analyzer
image:
name: "$SECURE_ANALYZERS_PREFIX/tslint:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false'
when: never
- if: $CI_COMMIT_BRANCH &&
$GITLAB_FEATURES =~ /\bsast\b/ &&
$SAST_DEFAULT_ANALYZERS =~ /tslint/
exists:
- '**/*.ts'

View File

@ -13,7 +13,7 @@
variables:
SECURE_BINARIES_ANALYZERS: >-
bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, tslint, secrets, sobelow, pmd-apex, kubesec,
bandit, brakeman, gosec, spotbugs, flawfinder, phpcs-security-audit, security-code-scan, nodejs-scan, eslint, secrets, sobelow, pmd-apex, kubesec,
bundler-audit, retire.js, gemnasium, gemnasium-maven, gemnasium-python,
klar, clair-vulnerabilities-db,
license-finder,
@ -125,13 +125,6 @@ eslint:
- $SECURE_BINARIES_DOWNLOAD_IMAGES == "true" &&
$SECURE_BINARIES_ANALYZERS =~ /\beslint\b/
tslint:
extends: .download_images
only:
variables:
- $SECURE_BINARIES_DOWNLOAD_IMAGES == "true" &&
$SECURE_BINARIES_ANALYZERS =~ /\btslint\b/
secrets:
extends: .download_images
only:

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
module Gitlab
# UpdatedNotesPaginator implements a rudimentary form of keyset pagination on
# top of a notes relation that has been initialized with a `last_fetched_at`
# value. This class will attempt to limit the number of notes returned, and
# specify a new value for `last_fetched_at` that will pick up where the last
# page of notes left off.
class UpdatedNotesPaginator
LIMIT = 50
MICROSECOND = 1_000_000
attr_reader :next_fetched_at, :notes
def initialize(relation, last_fetched_at:)
@last_fetched_at = last_fetched_at
@now = Time.current
notes, more = fetch_page(relation)
if more
init_middle_page(notes)
else
init_final_page(notes)
end
end
def metadata
{ last_fetched_at: next_fetched_at_microseconds, more: more }
end
private
attr_reader :last_fetched_at, :more, :now
def next_fetched_at_microseconds
(next_fetched_at.to_i * MICROSECOND) + next_fetched_at.usec
end
def fetch_page(relation)
relation = relation.by_updated_at
notes = relation.at_most(LIMIT + 1).to_a
return [notes, false] unless notes.size > LIMIT
marker = notes.pop # Remove the marker note
# Although very unlikely, it is possible that more notes with the same
# updated_at may exist, e.g., if created in bulk. Add them all to the page
# if this is detected, so pagination won't get stuck indefinitely
if notes.last.updated_at == marker.updated_at
notes += relation
.with_updated_at(marker.updated_at)
.id_not_in(notes.map(&:id))
.to_a
end
[notes, true]
end
def init_middle_page(notes)
@more = true
# The fetch overlap can be ignored if we're in an intermediate page.
@next_fetched_at = notes.last.updated_at + NotesFinder::FETCH_OVERLAP
@notes = notes
end
def init_final_page(notes)
@more = false
@next_fetched_at = now
@notes = notes
end
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module ProductAnalytics
class CollectorApp
def call(env)
request = Rack::Request.new(env)
params = request.params
return not_found unless EventParams.has_required_params?(params)
# Product analytics feature is behind a flag and is disabled by default.
# We expect limited amount of projects with this feature enabled in first release.
# Since collector has no authentication we temporary prevent recording of events
# for project without the feature enabled. During increase of feature adoption, this
# check will be removed for better performance.
project = Project.find(params['aid'].to_i)
return not_found unless Feature.enabled?(:product_analytics, project, default_enabled: false)
# Snowplow tracker has own format of events.
# We need to convert them to match the schema of our database.
event_params = EventParams.parse_event_params(params)
if ProductAnalyticsEvent.create(event_params)
ok
else
not_found
end
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::RecordNotFound
not_found
end
def ok
[200, {}, []]
end
def not_found
[404, {}, []]
end
end
end

View File

@ -0,0 +1,51 @@
# frozen_string_literal: true
module ProductAnalytics
# Converts params from Snowplow tracker to one compatible with
# GitLab ProductAnalyticsEvent model. The field naming corresponds
# with snowplow event model. Only project_id is GitLab specific.
#
# For information on what each field is you can check next resources:
# * Snowplow tracker protocol: https://github.com/snowplow/snowplow/wiki/snowplow-tracker-protocol
# * Canonical event model: https://github.com/snowplow/snowplow/wiki/canonical-event-model
class EventParams
def self.parse_event_params(params)
{
project_id: params['aid'],
platform: params['p'],
collector_tstamp: Time.zone.now,
event_id: params['eid'],
v_tracker: params['tv'],
v_collector: Gitlab::VERSION,
v_etl: Gitlab::VERSION,
os_timezone: params['tz'],
name_tracker: params['tna'],
br_lang: params['lang'],
doc_charset: params['cs'],
br_features_pdf: Gitlab::Utils.to_boolean(params['f_pdf']),
br_features_flash: Gitlab::Utils.to_boolean(params['f_fla']),
br_features_java: Gitlab::Utils.to_boolean(params['f_java']),
br_features_director: Gitlab::Utils.to_boolean(params['f_dir']),
br_features_quicktime: Gitlab::Utils.to_boolean(params['f_qt']),
br_features_realplayer: Gitlab::Utils.to_boolean(params['f_realp']),
br_features_windowsmedia: Gitlab::Utils.to_boolean(params['f_wma']),
br_features_gears: Gitlab::Utils.to_boolean(params['f_gears']),
br_features_silverlight: Gitlab::Utils.to_boolean(params['f_ag']),
br_colordepth: params['cd'],
br_cookies: Gitlab::Utils.to_boolean(params['cookie']),
dvce_created_tstamp: params['dtm'],
br_viewheight: params['vp'],
domain_sessionidx: params['vid'],
domain_sessionid: params['sid'],
domain_userid: params['duid'],
user_fingerprint: params['fp'],
page_referrer: params['refr'],
page_url: params['url']
}
end
def self.has_required_params?(params)
params['aid'].present? && params['eid'].present?
end
end
end

View File

@ -2106,6 +2106,9 @@ msgstr ""
msgid "AlertManagement|There was an error displaying the alerts. Confirm your endpoint's configuration details to ensure alerts appear."
msgstr ""
msgid "AlertManagement|There was an error while updating the To Do of the alert."
msgstr ""
msgid "AlertManagement|There was an error while updating the assignee(s) list. Please try again."
msgstr ""
@ -10357,9 +10360,6 @@ msgstr ""
msgid "Filter pipelines"
msgstr ""
msgid "Filter projects"
msgstr ""
msgid "Filter results"
msgstr ""
@ -10567,18 +10567,6 @@ msgstr ""
msgid "From <code>%{source_title}</code> into"
msgstr ""
msgid "From Bitbucket"
msgstr ""
msgid "From Bitbucket Server"
msgstr ""
msgid "From FogBugz"
msgstr ""
msgid "From GitLab.com"
msgstr ""
msgid "From Google Code"
msgstr ""
@ -11053,9 +11041,6 @@ msgstr ""
msgid "Getting started with releases"
msgstr ""
msgid "Git"
msgstr ""
msgid "Git LFS is not enabled on this GitLab server, contact your admin."
msgstr ""
@ -14546,6 +14531,9 @@ msgstr ""
msgid "Merge request dependencies"
msgstr ""
msgid "Merge request was scheduled to merge after pipeline succeeds"
msgstr ""
msgid "Merge requests"
msgstr ""
@ -16249,9 +16237,6 @@ msgstr ""
msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
@ -17305,9 +17290,6 @@ msgstr ""
msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
msgstr ""
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr ""
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr ""
@ -19234,9 +19216,6 @@ msgstr ""
msgid "Queued"
msgstr ""
msgid "Quick actions"
msgstr ""
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
@ -20485,9 +20464,6 @@ msgstr ""
msgid "Search"
msgstr ""
msgid "Search Button"
msgstr ""
msgid "Search Jira issues"
msgstr ""
@ -24801,9 +24777,6 @@ msgstr ""
msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
msgstr ""
msgid "To this GitLab instance"
msgstr ""
msgid "To view all %{scannedResourcesCount} scanned URLs, please download the CSV file"
msgstr ""
@ -26826,6 +26799,9 @@ msgstr ""
msgid "You"
msgstr ""
msgid "You already have pending todo for this alert"
msgstr ""
msgid "You are about to delete %{domain} from your instance. This domain will no longer be available to any Knative application."
msgstr ""
@ -27105,6 +27081,9 @@ msgstr ""
msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
msgstr ""
msgid "You have insufficient permissions to create a Todo for this alert"
msgstr ""
msgid "You have no permissions"
msgstr ""
@ -28914,6 +28893,9 @@ msgstr ""
msgid "vulnerability|dismissed"
msgstr ""
msgid "was scheduled to merge after pipeline succeeds by"
msgstr ""
msgid "wiki page"
msgstr ""

View File

@ -55,20 +55,6 @@ RSpec.describe DashboardController do
expect(json_response['count']).to eq(6)
end
describe 'design_activity_events feature flag' do
context 'it is off' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'excludes design activity' do
get :activity, params: { format: :json }
expect(json_response['count']).to eq(4)
end
end
end
end
context 'when user has no permission to see the event' do

View File

@ -1164,18 +1164,6 @@ RSpec.describe GroupsController do
expect(json_response['count']).to eq(3)
end
context 'the design_activity_events feature flag is disabled' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'does not include the design activity' do
get_activity
expect(json_response['count']).to eq(1)
end
end
end
describe 'GET #issues' do

View File

@ -58,12 +58,12 @@ RSpec.describe Import::BitbucketController do
before do
@repo = double(name: 'vim', slug: 'vim', owner: 'asd', full_name: 'asd/vim', clone_url: 'http://test.host/demo/url.git', 'valid?' => true)
@invalid_repo = double(name: 'mercurialrepo', slug: 'mercurialrepo', owner: 'asd', full_name: 'asd/mercurialrepo', clone_url: 'http://test.host/demo/mercurialrepo.git', 'valid?' => false)
allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
assign_session_tokens
stub_feature_flags(new_import_ui: false)
end
it_behaves_like 'import controller with new_import_ui feature flag' do
it_behaves_like 'import controller status' do
before do
allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
end
@ -75,44 +75,16 @@ RSpec.describe Import::BitbucketController do
let(:client_repos_field) { :repos }
end
context 'with new_import_ui feature flag enabled' do
before do
stub_feature_flags(new_import_ui: true)
allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
end
it 'returns invalid repos' do
allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo, @invalid_repo])
it 'returns invalid repos' do
allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo, @invalid_repo])
get :status, format: :json
get :status, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['incompatible_repos'].length).to eq(1)
expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
expect(json_response['provider_repos'].length).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
end
it "assigns variables" do
@project = create(:project, import_type: 'bitbucket', creator_id: user.id)
allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([@repo])
expect(assigns(:incompatible_repos)).to eq([])
end
it "does not show already added project" do
@project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim')
allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([])
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['incompatible_repos'].length).to eq(1)
expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
expect(json_response['provider_repos'].length).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
context 'when filtering' do

View File

@ -148,28 +148,21 @@ RSpec.describe Import::BitbucketServerController do
@invalid_repo = double(slug: 'invalid', project_key: 'foobar', full_name: 'asd/foobar', "valid?" => false, browse_url: 'http://bad-repo', name: 'invalid')
@created_repo = double(slug: 'created', project_key: 'existing', full_name: 'group/created', "valid?" => true, browse_url: 'http://existing')
assign_session_tokens
stub_feature_flags(new_import_ui: false)
end
context 'with new_import_ui feature flag enabled' do
before do
stub_feature_flags(new_import_ui: true)
end
it 'returns invalid repos' do
allow(client).to receive(:repos).with(filter: nil, limit: 25, page_offset: 0).and_return([@repo, @invalid_repo])
it 'returns invalid repos' do
allow(client).to receive(:repos).with(filter: nil, limit: 25, page_offset: 0).and_return([@repo, @invalid_repo])
get :status, format: :json
get :status, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['incompatible_repos'].length).to eq(1)
expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
expect(json_response['provider_repos'].length).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['incompatible_repos'].length).to eq(1)
expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
expect(json_response['provider_repos'].length).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
it_behaves_like 'import controller with new_import_ui feature flag' do
it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.full_name }
let(:import_source) { @repo.browse_url }
@ -177,47 +170,14 @@ RSpec.describe Import::BitbucketServerController do
let(:client_repos_field) { :repos }
end
it 'assigns repository categories' do
created_project = create(:project, :import_finished, import_type: 'bitbucket_server', creator_id: user.id, import_source: @created_repo.browse_url)
expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
expect(repos).to receive(:current_page).and_return(1)
expect(repos).to receive(:next_page).and_return(2)
expect(repos).to receive(:prev_page).and_return(nil)
expect(client).to receive(:repos).and_return(repos)
get :status
expect(assigns(:already_added_projects)).to eq([created_project])
expect(assigns(:repos)).to eq([@repo])
expect(assigns(:incompatible_repos)).to eq([@invalid_repo])
end
context 'when filtering' do
let(:filter) { 'test' }
it 'passes filter param to bitbucket client' do
expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return(repos)
expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return([@repo])
get :status, params: { filter: filter }, as: :json
end
end
end
describe 'GET jobs' do
before do
assign_session_tokens
end
it 'returns a list of imported projects' do
created_project = create(:project, import_type: 'bitbucket_server', creator_id: user.id)
get :jobs
expect(json_response.count).to eq(1)
expect(json_response.first['id']).to eq(created_project.id)
expect(json_response.first['import_status']).to eq('none')
end
end
end

View File

@ -82,36 +82,15 @@ RSpec.describe Import::FogbugzController do
before do
@repo = OpenStruct.new(id: 'demo', name: 'vim')
stub_client(valid?: true)
stub_feature_flags(new_import_ui: false)
end
it_behaves_like 'import controller with new_import_ui feature flag' do
it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.name }
let(:provider_name) { 'fogbugz' }
let(:client_repos_field) { :repos }
end
it 'assigns variables' do
@project = create(:project, import_type: 'fogbugz', creator_id: user.id)
stub_client(repos: [@repo])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([@repo])
end
it 'does not show already added project' do
@project = create(:project, import_type: 'fogbugz', creator_id: user.id, import_source: 'vim')
stub_client(repos: [@repo])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([])
end
end
describe 'POST create' do

View File

@ -36,36 +36,15 @@ RSpec.describe Import::GitlabController do
before do
@repo = OpenStruct.new(id: 1, path: 'vim', path_with_namespace: 'asd/vim', web_url: 'https://gitlab.com/asd/vim')
assign_session_token
stub_feature_flags(new_import_ui: false)
end
it_behaves_like 'import controller with new_import_ui feature flag' do
it_behaves_like 'import controller status' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.path_with_namespace }
let(:provider_name) { 'gitlab' }
let(:client_repos_field) { :projects }
end
it "assigns variables" do
@project = create(:project, import_type: 'gitlab', creator_id: user.id)
stub_client(projects: [@repo])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([@repo])
end
it "does not show already added project" do
@project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim')
stub_client(projects: [@repo])
get :status
expect(assigns(:already_added_projects)).to eq([@project])
expect(assigns(:repos)).to eq([])
end
end
describe "POST create" do

View File

@ -38,9 +38,9 @@ RSpec.describe Projects::NotesController do
end
it 'passes last_fetched_at from headers to NotesFinder and MergeIntoNotesService' do
last_fetched_at = 3.hours.ago.to_i
last_fetched_at = Time.zone.at(3.hours.ago.to_i) # remove nanoseconds
request.headers['X-Last-Fetched-At'] = last_fetched_at
request.headers['X-Last-Fetched-At'] = microseconds(last_fetched_at)
expect(NotesFinder).to receive(:new)
.with(anything, hash_including(last_fetched_at: last_fetched_at))
@ -84,6 +84,81 @@ RSpec.describe Projects::NotesController do
end
end
context 'for multiple pages of notes', :aggregate_failures do
# 3 pages worth: 1 normal page, 1 oversized due to clashing updated_at,
# and a final, short page
let!(:page_1) { create_list(:note, 2, noteable: issue, project: project, updated_at: 3.days.ago) }
let!(:page_2) { create_list(:note, 3, noteable: issue, project: project, updated_at: 2.days.ago) }
let!(:page_3) { create_list(:note, 2, noteable: issue, project: project, updated_at: 1.day.ago) }
# Include a resource event in the middle page as well
let!(:resource_event) { create(:resource_state_event, issue: issue, user: user, created_at: 2.days.ago) }
let(:page_1_boundary) { microseconds(page_1.last.updated_at + NotesFinder::FETCH_OVERLAP) }
let(:page_2_boundary) { microseconds(page_2.last.updated_at + NotesFinder::FETCH_OVERLAP) }
around do |example|
Timecop.freeze do
example.run
end
end
before do
stub_const('Gitlab::UpdatedNotesPaginator::LIMIT', 2)
end
context 'feature flag enabled' do
before do
stub_feature_flags(paginated_notes: true)
end
it 'returns the first page of notes' do
get :index, params: request_params
expect(json_response['notes'].count).to eq(page_1.count)
expect(json_response['more']).to be_truthy
expect(json_response['last_fetched_at']).to eq(page_1_boundary)
expect(response.headers['Poll-Interval'].to_i).to eq(1)
end
it 'returns the second page of notes' do
request.headers['X-Last-Fetched-At'] = page_1_boundary
get :index, params: request_params
expect(json_response['notes'].count).to eq(page_2.count + 1) # resource event
expect(json_response['more']).to be_truthy
expect(json_response['last_fetched_at']).to eq(page_2_boundary)
expect(response.headers['Poll-Interval'].to_i).to eq(1)
end
it 'returns the final page of notes' do
request.headers['X-Last-Fetched-At'] = page_2_boundary
get :index, params: request_params
expect(json_response['notes'].count).to eq(page_3.count)
expect(json_response['more']).to be_falsy
expect(json_response['last_fetched_at']).to eq(microseconds(Time.zone.now))
expect(response.headers['Poll-Interval'].to_i).to be > 1
end
end
context 'feature flag disabled' do
before do
stub_feature_flags(paginated_notes: false)
end
it 'returns all notes' do
get :index, params: request_params
expect(json_response['notes'].count).to eq((page_1 + page_2 + page_3).size + 1)
expect(json_response['more']).to be_falsy
expect(json_response['last_fetched_at']).to eq(microseconds(Time.zone.now))
end
end
end
context 'for a discussion note' do
let(:project) { create(:project, :repository) }
let!(:note) { create(:discussion_note_on_merge_request, project: project) }
@ -870,4 +945,9 @@ RSpec.describe Projects::NotesController do
end
end
end
# Convert a time to an integer number of microseconds
def microseconds(time)
(time.to_i * 1_000_000) + time.usec
end
end

View File

@ -130,18 +130,6 @@ RSpec.describe ProjectsController do
expect(json_response['count']).to eq(1)
end
context 'the feature flag is disabled' do
before do
stub_feature_flags(design_activity_events: false)
end
it 'returns correct count' do
get_activity(project)
expect(json_response['count']).to eq(0)
end
end
end
end

View File

@ -123,7 +123,7 @@ RSpec.describe NotesFinder do
let!(:note1) { create :note_on_commit, project: project }
let!(:note2) { create :note_on_commit, project: project }
let(:commit) { note1.noteable }
let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago } }
it 'finds all notes' do
notes = described_class.new(user, params).execute
@ -172,7 +172,7 @@ RSpec.describe NotesFinder do
let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) }
let!(:confidential_note) { create(:note, noteable: confidential_issue, project: confidential_issue.project) }
let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } }
let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago } }
it 'returns notes if user can see the issue' do
expect(described_class.new(user, params).execute).to eq([confidential_note])
@ -204,7 +204,7 @@ RSpec.describe NotesFinder do
end
it 'returns the expected notes when last_fetched_at is given' do
params = { project: project, target: commit, last_fetched_at: 1.hour.ago.to_i }
params = { project: project, target: commit, last_fetched_at: 1.hour.ago }
expect(described_class.new(user, params).execute).to eq([note2])
end

View File

@ -37,28 +37,15 @@ RSpec.describe UserRecentEventsFinder do
expect(finder.execute).to be_empty
end
describe 'design_activity_events feature flag' do
describe 'design activity events' do
let_it_be(:event_a) { create(:design_event, author: project_owner) }
let_it_be(:event_b) { create(:design_event, author: project_owner) }
context 'the design_activity_events feature-flag is enabled' do
it 'only includes design events in enabled projects', :aggregate_failures do
events = finder.execute
it 'only includes design events', :aggregate_failures do
events = finder.execute
expect(events).to include(event_a)
expect(events).to include(event_b)
end
end
context 'the design_activity_events feature-flag is disabled' do
it 'excludes design events', :aggregate_failures do
stub_feature_flags(design_activity_events: false)
events = finder.execute
expect(events).not_to include(event_a)
expect(events).not_to include(event_b)
end
expect(events).to include(event_a)
expect(events).to include(event_b)
end
end
end

View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----

View File

@ -0,0 +1,100 @@
-----BEGIN CERTIFICATE-----
MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk
IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy
MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB
BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy
OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT
BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy
dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ
IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ
u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb
DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS
KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU
iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f
eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU
+PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa
MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp
ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3
d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy
gg13d3cudGhhd3RlLmRlgg53d3cudGhhd3RlLmNvbYIQd3d3LmRpZ2ljZXJ0LmNv
bYIYa2ItaW50ZXJuYWwuZGlnaWNlcnQuY29tghprbm93bGVkZ2ViYXNlLmRpZ2lj
ZXJ0LmNvbYIWa25vd2xlZGdlLmRpZ2ljZXJ0LmNvbYIPa2guZGlnaWNlcnQuY29t
ghlrbm93bGVkZ2VodWIuZGlnaWNlcnQuY29tghh3ZWJzZWN1cml0eS5kaWdpY2Vy
dC5jb22CFGNvbnRlbnQuZGlnaWNlcnQuY29tgg93d3cuZnJlZXNzbC5jb22CHHd3
dy53ZWJzZWN1cml0eS5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw
Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3JsMDSgMqAw
hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3Js
MEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwgYgGCCsGAQUFBwEBBHwwejAk
BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAC
hkZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5k
ZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggF8BgorBgEE
AdZ5AgQCBIIBbASCAWgBZgB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7I
DdwQAAABbtLkOs4AAAQDAEYwRAIgQ7gh393PInhYfPOhg/lF9yZNRdvjBeufFoG8
VnBuPNMCIBP8YGC83ig5ttw3ipSRjH0bKj4Ak5O4rynoql9Dy8x3AHYAVhQGmi/X
wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFu0uQ7VgAABAMARzBFAiEAhzE7
1c48wn3s/30IB4WgxfpLburH0Ku8cchv8QeqcgACIBrWpUlDD18AOfkPCOcB2kWU
vRXsdptVm3jPeU5TtDSoAHUAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e
0YUAAAFu0uQ60gAABAMARjBEAiBBpH5m7ntGKFTOFgSLcFXRDg66xJqerMy0gOHj
4TIBYAIgfFABPNy6P61hjiOWwjq73lvoEdAyh18GeFHIp0BgsWEwDQYJKoZIhvcN
AQELBQADggEBAInaSEqteyQA1zUKiXVqgffhHKZsUq9UnMows6X+UoFPoby9xqm6
IaY/77zaFZYwXJlP/SvrlbgTLHAdir3y38uhAlfPX4iRuwggOpFFF5hqDckzCm91
ocGnoG6sUY5mOqKu2vIcZkUQDe+K5gOxI6ME/4YwzWCIcTmBPQ6NQmqiFLPoQty1
gdbGCcLQNFCuNq4n5OK2NmBjcbtyT4gglat7C4+KV8RkEubZ+MkXzyDkpEXjjzsK
7iuNB0hRgyyhGzHrlZ/l0OLoT0Cb4I5PzzRSseFEyPKCC1WSF7aE9rFfUqhpqSAT
7NV7SEijYyFFtuZfz9RGglcqnRlAfgTy+tU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW
YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY
uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/
LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy
/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh
cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k
8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB
Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy
dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2
MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j
b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW
gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh
hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg
4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa
2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs
1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1
oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn
8TUoE6smftX3eg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW
YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY
uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/
LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy
/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh
cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k
8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB
Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF
BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy
dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2
MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j
b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW
gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh
hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg
4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa
2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs
1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1
oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn
8TUoE6smftX3eg==
-----END CERTIFICATE-----

Some files were not shown because too many files have changed in this diff Show More