Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
831b6108d2
commit
d5cf5cf4f7
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]}.`,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
mutation($projectPath: ID!, $iid: String!) {
|
||||
alertTodoCreate(input: { iid: $iid, projectPath: $projectPath }) {
|
||||
errors
|
||||
alert {
|
||||
iid
|
||||
}
|
||||
todo {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -144,11 +144,6 @@
|
|||
"label": "Spotbugs",
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name": "tslint",
|
||||
"label": "TSLint",
|
||||
"enabled" : true
|
||||
},
|
||||
{
|
||||
"name": "secrets",
|
||||
"label": "Secrets",
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;" }
|
||||
|
||||
%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;" }
|
||||
|
||||
%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} · %{help_link}").html_safe % { host: Gitlab.config.gitlab.host, manage_notifications_link: manage_notifications_link, help_link: help_link }
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add ability for user to manually create a todo for an alert
|
||||
merge_request: 34175
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Paginate the notes incremental fetch endpoint
|
||||
merge_request: 34628
|
||||
author:
|
||||
type: performance
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Clean up GitlabIssueTrackerService database records
|
||||
merge_request: 35221
|
||||
author:
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add API support for instance-level Kubernetes clusters
|
||||
merge_request: 36001
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Enable design activity events by default
|
||||
merge_request: 37107
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Merge tslint secure analyzer with eslint secure analyzer
|
||||
merge_request: 36400
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed issue (#198424) that prevented k8s authentication with intermediate certificates
|
||||
merge_request: 31254
|
||||
author: Abdelrahman Mohamed
|
||||
type: fixed
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -23812,6 +23812,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200623121135
|
||||
20200623141217
|
||||
20200623141544
|
||||
20200623142159
|
||||
20200623170000
|
||||
20200623185440
|
||||
20200624075411
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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` |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
```
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ want to configure.
|
|||
|
||||

|
||||
|
||||
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**.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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-----
|
||||
|
|
@ -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-----
|
||||
|
|
@ -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
Loading…
Reference in New Issue