Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e6a54b33a9
commit
efcaec8a14
|
@ -9,6 +9,7 @@ import initSearchSettings from '~/search_settings';
|
|||
import initSettingsPanels from '~/settings_panels';
|
||||
import setupTransferEdit from '~/transfer_edit';
|
||||
import UserCallout from '~/user_callout';
|
||||
import initTopicsTokenSelector from '~/projects/settings/topics';
|
||||
import initProjectPermissionsSettings from '../shared/permissions';
|
||||
import initProjectLoadingSpinner from '../shared/save_project_loader';
|
||||
|
||||
|
@ -28,3 +29,4 @@ setupTransferEdit('.js-project-transfer-form', 'select.select2');
|
|||
dirtySubmitFactory(document.querySelectorAll('.js-general-settings-form, .js-mr-settings-form'));
|
||||
|
||||
initSearchSettings();
|
||||
initTopicsTokenSelector();
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<script>
|
||||
import { GlTokenSelector, GlAvatarLabeled } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import searchProjectTopics from '../queries/project_topics_search.query.graphql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlTokenSelector,
|
||||
GlAvatarLabeled,
|
||||
},
|
||||
i18n: {
|
||||
placeholder: s__('ProjectSettings|Search for topic'),
|
||||
},
|
||||
props: {
|
||||
selected: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
topics: {
|
||||
query: searchProjectTopics,
|
||||
variables() {
|
||||
return {
|
||||
search: this.search,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return (
|
||||
data.topics?.nodes.filter(
|
||||
(topic) => !this.selectedTokens.some((token) => token.name === topic.name),
|
||||
) || []
|
||||
);
|
||||
},
|
||||
debounce: 250,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
topics: [],
|
||||
selectedTokens: this.selected,
|
||||
search: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
loading() {
|
||||
return this.$apollo.queries.topics.loading;
|
||||
},
|
||||
placeholderText() {
|
||||
return this.selectedTokens.length ? '' : this.$options.i18n.placeholder;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleEnter(event) {
|
||||
// Prevent form from submitting when adding a token
|
||||
if (event.target.value !== '') {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
filterTopics(searchTerm) {
|
||||
this.search = searchTerm;
|
||||
},
|
||||
onTokensUpdate(tokens) {
|
||||
this.$emit('update', tokens);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<gl-token-selector
|
||||
ref="tokenSelector"
|
||||
v-model="selectedTokens"
|
||||
:dropdown-items="topics"
|
||||
:loading="loading"
|
||||
allow-user-defined-tokens
|
||||
:placeholder="placeholderText"
|
||||
@keydown.enter="handleEnter"
|
||||
@text-input="filterTopics"
|
||||
@input="onTokensUpdate"
|
||||
>
|
||||
<template #dropdown-item-content="{ dropdownItem }">
|
||||
<gl-avatar-labeled
|
||||
:src="dropdownItem.avatarUrl"
|
||||
:entity-name="dropdownItem.name"
|
||||
:label="dropdownItem.name"
|
||||
:size="32"
|
||||
shape="rect"
|
||||
/>
|
||||
</template>
|
||||
</gl-token-selector>
|
||||
</template>
|
|
@ -0,0 +1,51 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createDefaultClient from '~/lib/graphql';
|
||||
import TopicsTokenSelector from './components/topics_token_selector.vue';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createDefaultClient(),
|
||||
});
|
||||
|
||||
export default () => {
|
||||
const el = document.querySelector('.js-topics-selector');
|
||||
|
||||
if (!el) return null;
|
||||
|
||||
const { hiddenInputId } = el.dataset;
|
||||
const hiddenInput = document.getElementById(hiddenInputId);
|
||||
|
||||
const selected = hiddenInput.value
|
||||
? hiddenInput.value.split(/,\s*/).map((token, index) => ({
|
||||
id: index,
|
||||
name: token,
|
||||
}))
|
||||
: [];
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
apolloProvider,
|
||||
render(createElement) {
|
||||
return createElement(TopicsTokenSelector, {
|
||||
props: {
|
||||
selected,
|
||||
},
|
||||
on: {
|
||||
update(tokens) {
|
||||
const value = tokens.map(({ name }) => name).join(', ');
|
||||
hiddenInput.value = value;
|
||||
// Dispatch `input` event so form submit button becomes active
|
||||
hiddenInput.dispatchEvent(
|
||||
new Event('input', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
query searchProjectTopics($search: String) {
|
||||
topics(search: $search) {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
avatarUrl
|
||||
}
|
||||
}
|
||||
}
|
|
@ -95,6 +95,8 @@ export default {
|
|||
noRefSelected: __('No tag selected'),
|
||||
dropdownHeader: __('Tag name'),
|
||||
searchPlaceholder: __('Search or create tag'),
|
||||
label: __('Tag name'),
|
||||
labelDescription: __('*Required'),
|
||||
},
|
||||
createFrom: {
|
||||
noRefSelected: __('No source selected'),
|
||||
|
@ -108,11 +110,12 @@ export default {
|
|||
<template>
|
||||
<div>
|
||||
<gl-form-group
|
||||
:label="__('Tag name')"
|
||||
:label-for="tagNameInputId"
|
||||
data-testid="tag-name-field"
|
||||
:state="!showTagNameValidationError"
|
||||
:invalid-feedback="__('Tag name is required')"
|
||||
:label="$options.translations.tagName.label"
|
||||
:label-for="tagNameInputId"
|
||||
:label-description="$options.translations.tagName.labelDescription"
|
||||
>
|
||||
<form-field-container>
|
||||
<ref-selector
|
||||
|
|
|
@ -40,7 +40,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-gl-tooltip.left.viewport :class="containerClass" :title="tooltipText" @click="click">
|
||||
<div v-gl-tooltip.left.viewport="tooltipText" :class="containerClass" @click="click">
|
||||
<gl-icon v-if="showIcon" name="calendar" />
|
||||
<slot>
|
||||
<span> {{ text }} </span>
|
||||
|
|
|
@ -43,12 +43,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-gl-tooltip.left.viewport
|
||||
:title="labelsList"
|
||||
class="sidebar-collapsed-icon"
|
||||
@click="handleClick"
|
||||
>
|
||||
<div v-gl-tooltip.left.viewport="labelsList" class="sidebar-collapsed-icon" @click="handleClick">
|
||||
<gl-icon name="labels" />
|
||||
<span>{{ labels.length }}</span>
|
||||
</div>
|
||||
|
|
|
@ -12,4 +12,20 @@ kbd {
|
|||
border-image: none;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 -1px 0 var(--gray-200, $gray-200) inset;
|
||||
|
||||
&.flat {
|
||||
color: $code-color;
|
||||
background-color: $gray-100;
|
||||
border-color: var(--gray-10, $gray-10) var(--gray-10, $gray-10) var(--gray-50, $gray-50);
|
||||
box-shadow: none;
|
||||
border-radius: $border-radius-default;
|
||||
font-family: $monospace-font;
|
||||
font-size: $gl-font-size-small;
|
||||
line-height: 1;
|
||||
white-space: pre-wrap;
|
||||
// Safari
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ class GitlabSchema < GraphQL::Schema
|
|||
|
||||
# Tracers (order is important)
|
||||
use Gitlab::Graphql::Tracers::ApplicationContextTracer
|
||||
use Gitlab::Graphql::Tracers::MetricsTracer
|
||||
use Gitlab::Graphql::Tracers::LoggerTracer
|
||||
use Gitlab::Graphql::GenericTracing # Old tracer which will be removed eventually
|
||||
use Gitlab::Graphql::Tracers::TimerTracer
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class TopicsResolver < BaseResolver
|
||||
type Types::Projects::TopicType, null: true
|
||||
|
||||
argument :search, GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'Search query for topic name.'
|
||||
|
||||
def resolve(**args)
|
||||
if args[:search].present?
|
||||
::Projects::Topic.search(args[:search]).order_by_total_projects_count
|
||||
else
|
||||
::Projects::Topic.order_by_total_projects_count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Projects
|
||||
# rubocop: disable Graphql/AuthorizeTypes
|
||||
class TopicType < BaseObject
|
||||
graphql_name 'Topic'
|
||||
|
||||
field :id, GraphQL::Types::ID, null: false,
|
||||
description: 'ID of the topic.'
|
||||
|
||||
field :name, GraphQL::Types::String, null: false,
|
||||
description: 'Name of the topic.'
|
||||
|
||||
field :description, GraphQL::Types::String, null: true,
|
||||
description: 'Description of the topic.'
|
||||
markdown_field :description_html, null: true
|
||||
|
||||
field :avatar_url, GraphQL::Types::String, null: true,
|
||||
description: 'URL to avatar image file of the topic.'
|
||||
|
||||
def avatar_url
|
||||
object.avatar_url(only_path: false)
|
||||
end
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
end
|
|
@ -140,6 +140,11 @@ module Types
|
|||
null: true,
|
||||
resolver: Resolvers::BoardListResolver
|
||||
|
||||
field :topics, Types::Projects::TopicType.connection_type,
|
||||
null: true,
|
||||
resolver: Resolvers::TopicsResolver,
|
||||
description: "Find project topics."
|
||||
|
||||
def design_management
|
||||
DesignManagementObject.new(nil)
|
||||
end
|
||||
|
|
|
@ -14,12 +14,13 @@ module StorageHelper
|
|||
counter_repositories: storage_counter(statistics.repository_size),
|
||||
counter_wikis: storage_counter(statistics.wiki_size),
|
||||
counter_build_artifacts: storage_counter(statistics.build_artifacts_size),
|
||||
counter_pipeline_artifacts: storage_counter(statistics.pipeline_artifacts_size),
|
||||
counter_lfs_objects: storage_counter(statistics.lfs_objects_size),
|
||||
counter_snippets: storage_counter(statistics.snippets_size),
|
||||
counter_packages: storage_counter(statistics.packages_size),
|
||||
counter_uploads: storage_counter(statistics.uploads_size)
|
||||
}
|
||||
|
||||
_("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters
|
||||
_("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}") % counters
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,9 +11,7 @@ module Timebox
|
|||
include StripAttribute
|
||||
include FromUnion
|
||||
|
||||
TimeboxStruct = Struct.new(:title, :name, :id) do
|
||||
include GlobalID::Identification
|
||||
|
||||
TimeboxStruct = Struct.new(:title, :name, :id, :class_name) do
|
||||
# Ensure these models match the interface required for exporting
|
||||
def serializable_hash(_opts = {})
|
||||
{ title: title, name: name, id: id }
|
||||
|
@ -22,6 +20,10 @@ module Timebox
|
|||
def self.declarative_policy_class
|
||||
"TimeboxPolicy"
|
||||
end
|
||||
|
||||
def to_global_id
|
||||
::Gitlab::GlobalId.build(self, model_name: class_name, id: id)
|
||||
end
|
||||
end
|
||||
|
||||
# Represents a "No Timebox" state used for filtering Issues and Merge
|
||||
|
@ -33,10 +35,10 @@ module Timebox
|
|||
|
||||
included do
|
||||
# Defines the same constants above, but inside the including class.
|
||||
const_set :None, TimeboxStruct.new("No #{self.name}", "No #{self.name}", 0)
|
||||
const_set :Any, TimeboxStruct.new("Any #{self.name}", '', -1)
|
||||
const_set :Upcoming, TimeboxStruct.new('Upcoming', '#upcoming', -2)
|
||||
const_set :Started, TimeboxStruct.new('Started', '#started', -3)
|
||||
const_set :None, TimeboxStruct.new("No #{self.name}", "No #{self.name}", 0, self.name)
|
||||
const_set :Any, TimeboxStruct.new("Any #{self.name}", '', -1, self.name)
|
||||
const_set :Upcoming, TimeboxStruct.new('Upcoming', '#upcoming', -2, self.name)
|
||||
const_set :Started, TimeboxStruct.new('Started', '#started', -3, self.name)
|
||||
|
||||
alias_method :timebox_id, :id
|
||||
|
||||
|
|
|
@ -18,9 +18,10 @@ class ErrorTracking::Error < ApplicationRecord
|
|||
scope :for_status, -> (status) { where(status: status) }
|
||||
|
||||
validates :project, presence: true
|
||||
validates :name, presence: true
|
||||
validates :description, presence: true
|
||||
validates :actor, presence: true
|
||||
validates :name, presence: true, length: { maximum: 255 }
|
||||
validates :description, presence: true, length: { maximum: 1024 }
|
||||
validates :actor, presence: true, length: { maximum: 255 }
|
||||
validates :platform, length: { maximum: 255 }
|
||||
validates :status, presence: true
|
||||
|
||||
enum status: {
|
||||
|
|
|
@ -6,7 +6,9 @@ class ErrorTracking::ErrorEvent < ApplicationRecord
|
|||
validates :payload, json_schema: { filename: 'error_tracking_event_payload' }
|
||||
|
||||
validates :error, presence: true
|
||||
validates :description, presence: true
|
||||
validates :description, presence: true, length: { maximum: 1024 }
|
||||
validates :level, length: { maximum: 255 }
|
||||
validates :environment, length: { maximum: 255 }
|
||||
validates :occurred_at, presence: true
|
||||
|
||||
def stacktrace
|
||||
|
|
|
@ -14,7 +14,7 @@ class Integration < ApplicationRecord
|
|||
asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker datadog discord
|
||||
drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat irker jira
|
||||
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
|
||||
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
|
||||
pivotaltracker prometheus pushover redmine shimo slack slack_slash_commands teamcity unify_circuit webex_teams youtrack zentao
|
||||
].freeze
|
||||
|
||||
PROJECT_SPECIFIC_INTEGRATION_NAMES = %w[
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Integrations
|
||||
class Shimo < Integration
|
||||
prop_accessor :external_wiki_url
|
||||
validates :external_wiki_url, presence: true, public_url: true, if: :activated?
|
||||
|
||||
def render?
|
||||
valid? && activated?
|
||||
end
|
||||
|
||||
def title
|
||||
s_('Shimo|Shimo')
|
||||
end
|
||||
|
||||
def description
|
||||
s_('Shimo|Link to a Shimo Workspace from the sidebar.')
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
'shimo'
|
||||
end
|
||||
|
||||
# support for `test` method
|
||||
def execute(_data)
|
||||
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true, use_read_total_timeout: true)
|
||||
response.body if response.code == 200
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
|
||||
def self.supported_events
|
||||
%w()
|
||||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{
|
||||
type: 'text',
|
||||
name: 'external_wiki_url',
|
||||
title: s_('Shimo|Shimo Workspace URL'),
|
||||
required: true
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -189,6 +189,7 @@ class Project < ApplicationRecord
|
|||
has_one :prometheus_integration, class_name: 'Integrations::Prometheus', inverse_of: :project
|
||||
has_one :pushover_integration, class_name: 'Integrations::Pushover'
|
||||
has_one :redmine_integration, class_name: 'Integrations::Redmine'
|
||||
has_one :shimo_integration, class_name: 'Integrations::Shimo'
|
||||
has_one :slack_integration, class_name: 'Integrations::Slack'
|
||||
has_one :slack_slash_commands_integration, class_name: 'Integrations::SlackSlashCommands'
|
||||
has_one :teamcity_integration, class_name: 'Integrations::Teamcity'
|
||||
|
@ -1453,7 +1454,7 @@ class Project < ApplicationRecord
|
|||
end
|
||||
|
||||
def disabled_integrations
|
||||
[]
|
||||
[:shimo]
|
||||
end
|
||||
|
||||
def find_or_initialize_integration(name)
|
||||
|
|
|
@ -28,7 +28,18 @@ module ErrorTracking
|
|||
private
|
||||
|
||||
def event
|
||||
params[:event]
|
||||
@event ||= format_event(params[:event])
|
||||
end
|
||||
|
||||
def format_event(event)
|
||||
# Some SDK send exception payload as Array. For exmple Go lang SDK.
|
||||
# We need to convert it to hash format we expect.
|
||||
if event['exception'].is_a?(Array)
|
||||
exception = event['exception']
|
||||
event['exception'] = { 'values' => exception }
|
||||
end
|
||||
|
||||
event
|
||||
end
|
||||
|
||||
def exception
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
%li
|
||||
%button.js-shortcuts-modal-trigger{ type: "button" }
|
||||
= _("Keyboard shortcuts")
|
||||
%span.text-secondary.float-right{ "aria-hidden": "true" }= '?'.html_safe
|
||||
%kbd.flat.float-right{ "aria-hidden": "true" }= '?'.html_safe
|
||||
%li.divider
|
||||
%li
|
||||
= link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
- hidden_topics_field_id = 'project_topic_list_field'
|
||||
= form_for [@project], html: { multipart: true, class: "edit-project js-general-settings-form" }, authenticity_token: true do |f|
|
||||
%input{ name: 'update_section', type: 'hidden', value: 'js-general-settings' }
|
||||
|
||||
|
@ -15,9 +16,9 @@
|
|||
|
||||
.row
|
||||
.form-group.col-md-9
|
||||
= f.label :topics, _('Topics (optional)'), class: 'label-bold'
|
||||
= f.text_field :topics, value: @project.topic_list.join(', '), maxlength: 2000, class: "form-control gl-form-input"
|
||||
%p.form-text.text-muted= _('Separate topics with commas.')
|
||||
= f.label :topics, _('Topics'), class: 'label-bold'
|
||||
.js-topics-selector{ data: { hidden_input_id: hidden_topics_field_id } }
|
||||
= f.hidden_field :topics, value: @project.topic_list.join(', '), id: hidden_topics_field_id
|
||||
|
||||
.row
|
||||
.form-group.col-md-9
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
Rails.application.configure do
|
||||
# Rails 6.1
|
||||
config.action_dispatch.cookies_same_site_protection = nil # New default is :lax
|
||||
config.action_dispatch.ssl_default_redirect_status = nil # New default is 308
|
||||
ActiveSupport.utc_to_local_returns_utc_offset_times = false
|
||||
config.action_controller.urlsafe_csrf_tokens = false
|
||||
config.action_view.preload_links_header = false
|
||||
|
@ -31,5 +30,4 @@ Rails.application.configure do
|
|||
config.action_controller.per_form_csrf_tokens = false
|
||||
config.action_controller.forgery_protection_origin_check = false
|
||||
ActiveSupport.to_time_preserves_timezone = false
|
||||
config.ssl_options = {} # New default is { hsts: { subdomains: true } }
|
||||
end
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.projects_shimo_active
|
||||
name: count_all_projects_shimo_active
|
||||
description: Count of projects with active Shimo integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.5"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.groups_shimo_active
|
||||
name: count_all_groups_shimo_active
|
||||
description: Count of groups with active Shimo integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.5"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.instances_shimo_active
|
||||
name: count_all_instances_shimo_active
|
||||
description: Count of instances with active Shimo integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.5"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.projects_inheriting_shimo_active
|
||||
name: count_all_projects_inheriting_shimo_active
|
||||
description: Count of projects that inherit active Shimo integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.5"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
key_path: counts.groups_inheriting_shimo_active
|
||||
name: count_all_groups_inheriting_shimo_active
|
||||
description: Count of groups that inherit active Shimo integrations
|
||||
product_section: dev
|
||||
product_stage: ecosystem
|
||||
product_group: group::integrations
|
||||
product_category: integrations
|
||||
value_type: number
|
||||
status: active
|
||||
milestone: "14.5"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343386
|
||||
time_frame: all
|
||||
data_source: database
|
||||
data_category: optional
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
tier:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
|
@ -41,7 +41,7 @@ To enable the Atlassian OmniAuth provider for passwordless authentication you mu
|
|||
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings to enable single sign-on and add `atlassian_oauth2` as an OAuth provider.
|
||||
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings to enable single sign-on and add `atlassian_oauth2` as an OAuth provider.
|
||||
1. Add the provider configuration for Atlassian:
|
||||
|
||||
For Omnibus GitLab installations:
|
||||
|
|
|
@ -27,7 +27,7 @@ Authentiq generates a Client ID and the accompanying Client Secret for you to us
|
|||
sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings to enable single sign-on and add Authentiq as an OAuth provider.
|
||||
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings to enable single sign-on and add Authentiq as an OAuth provider.
|
||||
|
||||
1. Add the provider configuration for Authentiq:
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ The following steps enable AWS Cognito as an authentication provider:
|
|||
|
||||
## Configure GitLab
|
||||
|
||||
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings.
|
||||
1. On your GitLab server, open the configuration file.
|
||||
|
||||
**For Omnibus installations**
|
||||
|
@ -88,4 +88,4 @@ Your sign-in page should now display a Cognito button below the regular sign-in
|
|||
To begin the authentication process, click the icon, and AWS Cognito asks the user to sign in and authorize the GitLab application.
|
||||
If successful, the user is redirected and signed in to your GitLab instance.
|
||||
|
||||
For more information, see the [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration).
|
||||
For more information, see [Configure initial settings](../../integration/omniauth.md#configure-initial-settings).
|
||||
|
|
|
@ -36,7 +36,7 @@ this provider also allows Crowd authentication for Git-over-https requests.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration)
|
||||
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings)
|
||||
for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
|
|
@ -25,7 +25,7 @@ JWT will provide you with a secret key for you to use.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings.
|
||||
1. Add the provider configuration.
|
||||
|
||||
For Omnibus GitLab:
|
||||
|
|
|
@ -27,7 +27,7 @@ The OpenID Connect provides you with a client's details and secret for you to us
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
See [Configure initial settings](../../integration/omniauth.md#configure-initial-settings) for initial settings.
|
||||
|
||||
1. Add the provider configuration.
|
||||
|
||||
|
@ -228,7 +228,7 @@ Azure B2C [offers two ways of defining the business logic for logging in a user]
|
|||
|
||||
While cumbersome to configure, custom policies are required because
|
||||
standard Azure B2C user flows [do not send the OpenID `email` claim](https://github.com/MicrosoftDocs/azure-docs/issues/16566). In
|
||||
other words, they do not work with the [`allow_single_sign_on` or `auto_link_user` parameters](../../integration/omniauth.md#initial-omniauth-configuration).
|
||||
other words, they do not work with the [`allow_single_sign_on` or `auto_link_user` parameters](../../integration/omniauth.md#configure-initial-settings).
|
||||
With a standard Azure B2C policy, GitLab cannot create a new account or
|
||||
link to an existing one with an email address.
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ Confirm the following are all true:
|
|||
successfully creates the project but doesn't create the README.
|
||||
- When [tailing the logs](https://docs.gitlab.com/omnibus/settings/logs.html#tail-logs-in-a-console-on-the-server)
|
||||
on a Gitaly client and reproducing the error, you get `401` errors
|
||||
when reaching the [`/api/v4/internal/allowed`](../../development/internal_api.md) endpoint:
|
||||
when reaching the [`/api/v4/internal/allowed`](../../development/internal_api/index.md) endpoint:
|
||||
|
||||
```shell
|
||||
# api_json.log
|
||||
|
|
|
@ -119,7 +119,7 @@ For most JSON requests, POST, PUT, PATCH, and DELETE are blocked, and the API re
|
|||
| POST | `/admin/session`, `/admin/session/destroy` | To allow [Administrator mode for GitLab administrators](https://gitlab.com/groups/gitlab-org/-/epics/2158) |
|
||||
| POST | Paths ending with `/compare`| Git revision routes. |
|
||||
| POST | `.git/git-upload-pack` | To allow Git pull/clone. |
|
||||
| POST | `/api/v4/internal` | [internal API routes](../../development/internal_api.md) |
|
||||
| POST | `/api/v4/internal` | [internal API routes](../../development/internal_api/index.md) |
|
||||
| POST | `/admin/sidekiq` | To allow management of background jobs in the Admin UI |
|
||||
| POST | `/admin/geo` | To allow updating Geo Nodes in the administrator UI |
|
||||
| POST | `/api/v4/geo_replication`| To allow certain Geo-specific administrator UI actions on secondary sites |
|
||||
|
|
|
@ -258,7 +258,7 @@ separate Rails process to debug the issue:
|
|||
### GitLab: API is not accessible
|
||||
|
||||
This often occurs when GitLab Shell attempts to request authorization via the
|
||||
[internal API](../../development/internal_api.md) (for example, `http://localhost:8080/api/v4/internal/allowed`), and
|
||||
[internal API](../../development/internal_api/index.md) (for example, `http://localhost:8080/api/v4/internal/allowed`), and
|
||||
something in the check fails. There are many reasons why this may happen:
|
||||
|
||||
1. Timeout connecting to a database (for example, PostgreSQL or Redis)
|
||||
|
@ -275,7 +275,7 @@ strace -ttTfyyy -s 1024 -p <PID of puma worker> -o /tmp/puma.txt
|
|||
|
||||
If you cannot isolate which Unicorn worker is the issue, try to run `strace`
|
||||
on all the Unicorn workers to see where the
|
||||
[`/internal/allowed`](../../development/internal_api.md) endpoint gets stuck:
|
||||
[`/internal/allowed`](../../development/internal_api/index.md) endpoint gets stuck:
|
||||
|
||||
```shell
|
||||
ps auwx | grep puma | awk '{ print " -p " $2}' | xargs strace -ttTfyyy -s 1024 -o /tmp/puma.txt
|
||||
|
|
|
@ -419,6 +419,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="querytimelogsstarttime"></a>`startTime` | [`Time`](#time) | List timelogs within a time range where the logged time is equal to or after startTime. |
|
||||
| <a id="querytimelogsusername"></a>`username` | [`String`](#string) | List timelogs for a user. |
|
||||
|
||||
### `Query.topics`
|
||||
|
||||
Find project topics.
|
||||
|
||||
Returns [`TopicConnection`](#topicconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="querytopicssearch"></a>`search` | [`String`](#string) | Search query for topic name. |
|
||||
|
||||
### `Query.usageTrendsMeasurements`
|
||||
|
||||
Get statistics on the instance.
|
||||
|
@ -5676,6 +5692,29 @@ The edge type for [`ContainerRepositoryTag`](#containerrepositorytag).
|
|||
| <a id="containerrepositorytagedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="containerrepositorytagedgenode"></a>`node` | [`ContainerRepositoryTag`](#containerrepositorytag) | The item at the end of the edge. |
|
||||
|
||||
#### `CoverageFuzzingCorpusConnection`
|
||||
|
||||
The connection type for [`CoverageFuzzingCorpus`](#coveragefuzzingcorpus).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="coveragefuzzingcorpusconnectionedges"></a>`edges` | [`[CoverageFuzzingCorpusEdge]`](#coveragefuzzingcorpusedge) | A list of edges. |
|
||||
| <a id="coveragefuzzingcorpusconnectionnodes"></a>`nodes` | [`[CoverageFuzzingCorpus]`](#coveragefuzzingcorpus) | A list of nodes. |
|
||||
| <a id="coveragefuzzingcorpusconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `CoverageFuzzingCorpusEdge`
|
||||
|
||||
The edge type for [`CoverageFuzzingCorpus`](#coveragefuzzingcorpus).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="coveragefuzzingcorpusedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="coveragefuzzingcorpusedgenode"></a>`node` | [`CoverageFuzzingCorpus`](#coveragefuzzingcorpus) | The item at the end of the edge. |
|
||||
|
||||
#### `CustomEmojiConnection`
|
||||
|
||||
The connection type for [`CustomEmoji`](#customemoji).
|
||||
|
@ -7690,6 +7729,29 @@ The edge type for [`Todo`](#todo).
|
|||
| <a id="todoedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="todoedgenode"></a>`node` | [`Todo`](#todo) | The item at the end of the edge. |
|
||||
|
||||
#### `TopicConnection`
|
||||
|
||||
The connection type for [`Topic`](#topic).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="topicconnectionedges"></a>`edges` | [`[TopicEdge]`](#topicedge) | A list of edges. |
|
||||
| <a id="topicconnectionnodes"></a>`nodes` | [`[Topic]`](#topic) | A list of nodes. |
|
||||
| <a id="topicconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `TopicEdge`
|
||||
|
||||
The edge type for [`Topic`](#topic).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="topicedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="topicedgenode"></a>`node` | [`Topic`](#topic) | The item at the end of the edge. |
|
||||
|
||||
#### `TreeConnection`
|
||||
|
||||
The connection type for [`Tree`](#tree).
|
||||
|
@ -8991,6 +9053,17 @@ A tag from a container repository.
|
|||
| <a id="containerrepositorytagshortrevision"></a>`shortRevision` | [`String`](#string) | Short revision of the tag. |
|
||||
| <a id="containerrepositorytagtotalsize"></a>`totalSize` | [`BigInt`](#bigint) | Size of the tag. |
|
||||
|
||||
### `CoverageFuzzingCorpus`
|
||||
|
||||
Corpus for a coverage fuzzing job.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="coveragefuzzingcorpusid"></a>`id` | [`AppSecFuzzingCoverageCorpusID!`](#appsecfuzzingcoveragecorpusid) | ID of the corpus. |
|
||||
| <a id="coveragefuzzingcorpuspackage"></a>`package` | [`PackageDetailsType!`](#packagedetailstype) | Package of the corpus. |
|
||||
|
||||
### `CurrentLicense`
|
||||
|
||||
Represents the current license.
|
||||
|
@ -12763,6 +12836,7 @@ Represents vulnerability finding of a security report on the pipeline.
|
|||
| <a id="projectcontainerexpirationpolicy"></a>`containerExpirationPolicy` | [`ContainerExpirationPolicy`](#containerexpirationpolicy) | Container expiration policy of the project. |
|
||||
| <a id="projectcontainerregistryenabled"></a>`containerRegistryEnabled` | [`Boolean`](#boolean) | Indicates if Container Registry is enabled for the current user. |
|
||||
| <a id="projectcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the project. |
|
||||
| <a id="projectcorpuses"></a>`corpuses` | [`CoverageFuzzingCorpusConnection`](#coveragefuzzingcorpusconnection) | Find corpuses of the project. Available only when feature flag `corpus_management` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
|
||||
| <a id="projectcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of the project creation. |
|
||||
| <a id="projectdastscannerprofiles"></a>`dastScannerProfiles` | [`DastScannerProfileConnection`](#dastscannerprofileconnection) | DAST scanner profiles associated with the project. (see [Connections](#connections)) |
|
||||
| <a id="projectdastsiteprofiles"></a>`dastSiteProfiles` | [`DastSiteProfileConnection`](#dastsiteprofileconnection) | DAST Site Profiles associated with the project. (see [Connections](#connections)) |
|
||||
|
@ -14807,6 +14881,18 @@ Representing a to-do entry.
|
|||
| <a id="todostate"></a>`state` | [`TodoStateEnum!`](#todostateenum) | State of the to-do item. |
|
||||
| <a id="todotargettype"></a>`targetType` | [`TodoTargetEnum!`](#todotargetenum) | Target type of the to-do item. |
|
||||
|
||||
### `Topic`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="topicavatarurl"></a>`avatarUrl` | [`String`](#string) | URL to avatar image file of the topic. |
|
||||
| <a id="topicdescription"></a>`description` | [`String`](#string) | Description of the topic. |
|
||||
| <a id="topicdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
|
||||
| <a id="topicid"></a>`id` | [`ID!`](#id) | ID of the topic. |
|
||||
| <a id="topicname"></a>`name` | [`String!`](#string) | Name of the topic. |
|
||||
|
||||
### `Tree`
|
||||
|
||||
#### Fields
|
||||
|
@ -16819,6 +16905,7 @@ State of a Sentry error.
|
|||
| <a id="servicetypeprometheus_service"></a>`PROMETHEUS_SERVICE` | PrometheusService type. |
|
||||
| <a id="servicetypepushover_service"></a>`PUSHOVER_SERVICE` | PushoverService type. |
|
||||
| <a id="servicetyperedmine_service"></a>`REDMINE_SERVICE` | RedmineService type. |
|
||||
| <a id="servicetypeshimo_service"></a>`SHIMO_SERVICE` | ShimoService type. |
|
||||
| <a id="servicetypeslack_service"></a>`SLACK_SERVICE` | SlackService type. |
|
||||
| <a id="servicetypeslack_slash_commands_service"></a>`SLACK_SLASH_COMMANDS_SERVICE` | SlackSlashCommandsService type. |
|
||||
| <a id="servicetypeteamcity_service"></a>`TEAMCITY_SERVICE` | TeamcityService type. |
|
||||
|
@ -17137,6 +17224,12 @@ A `AnalyticsDevopsAdoptionEnabledNamespaceID` is a global ID. It is encoded as a
|
|||
|
||||
An example `AnalyticsDevopsAdoptionEnabledNamespaceID` is: `"gid://gitlab/Analytics::DevopsAdoption::EnabledNamespace/1"`.
|
||||
|
||||
### `AppSecFuzzingCoverageCorpusID`
|
||||
|
||||
A `AppSecFuzzingCoverageCorpusID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `AppSecFuzzingCoverageCorpusID` is: `"gid://gitlab/AppSec::Fuzzing::Coverage::Corpus/1"`.
|
||||
|
||||
### `AuditEventsExternalAuditEventDestinationID`
|
||||
|
||||
A `AuditEventsExternalAuditEventDestinationID` is a global ID. It is encoded as a string.
|
||||
|
|
|
@ -426,8 +426,11 @@ Use `include:local` instead of symbolic links.
|
|||
|
||||
**Keyword type**: Global keyword.
|
||||
|
||||
**Possible inputs**: A full path relative to the root directory (`/`).
|
||||
The YAML file must have the extension `.yml` or `.yaml`. Wildcard paths (`*` and `**`) are supported.
|
||||
**Possible inputs**:
|
||||
|
||||
- A full path relative to the root directory (`/`).
|
||||
- The YAML file must have the extension `.yml` or `.yaml`.
|
||||
- You can [use `*` and `**` wildcards in the file path](includes.md#use-includelocal-with-wildcard-file-paths).
|
||||
|
||||
**Example of `include:local`**:
|
||||
|
||||
|
@ -449,10 +452,6 @@ include: '.gitlab-ci-production.yml'
|
|||
- All [nested includes](includes.md#use-nested-includes) are executed in the scope of the same project,
|
||||
so you can use local, project, remote, or template includes.
|
||||
|
||||
**Related topics**:
|
||||
|
||||
- [Use `include:local` with wildcard file paths](includes.md#use-includelocal-with-wildcard-file-paths).
|
||||
|
||||
#### `include:file`
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53903) in GitLab 11.7.
|
||||
|
|
|
@ -240,7 +240,7 @@ it's own file in the [`validators`](https://gitlab.com/gitlab-org/gitlab/-/blob/
|
|||
|
||||
## Internal API
|
||||
|
||||
The [internal API](internal_api.md) is documented for internal use. Please keep it up to date so we know what endpoints
|
||||
The [internal API](internal_api/index.md) is documented for internal use. Please keep it up to date so we know what endpoints
|
||||
different components are making use of.
|
||||
|
||||
## Avoiding N+1 problems
|
||||
|
|
|
@ -976,12 +976,12 @@ in Rails, scheduled to run whenever an SSH key is modified by a user.
|
|||
instead of keys. In this case, `AuthorizedKeysCommand` is replaced with an
|
||||
`AuthorizedPrincipalsCommand`. This extracts a username from the certificate
|
||||
without using the Rails internal API, which is used instead of `key_id` in the
|
||||
[`/api/internal/allowed`](internal_api.md) call later.
|
||||
[`/api/internal/allowed`](internal_api/index.md) call later.
|
||||
|
||||
GitLab Shell also has a few operations that do not involve Gitaly, such as
|
||||
resetting two-factor authentication codes. These are handled in the same way,
|
||||
except there is no round-trip into Gitaly - Rails performs the action as part
|
||||
of the [internal API](internal_api.md) call, and GitLab Shell streams the
|
||||
of the [internal API](internal_api/index.md) call, and GitLab Shell streams the
|
||||
response back to the user directly.
|
||||
|
||||
## System layout
|
||||
|
|
|
@ -49,8 +49,8 @@ METHOD /endpoint
|
|||
|
||||
Supported attributes:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:---------|:---------|:----------------------|
|
||||
| Attribute | Type | Required | Description |
|
||||
| :---------- | :------- | :--------------------- | :-------------------- |
|
||||
| `attribute` | datatype | **{check-circle}** Yes | Detailed description. |
|
||||
| `attribute` | datatype | **{dotted-circle}** No | Detailed description. |
|
||||
| `attribute` | datatype | **{dotted-circle}** No | Detailed description. |
|
||||
|
@ -80,16 +80,23 @@ to describe the GitLab release that introduced the API call.
|
|||
Use the following table headers to describe the methods. Attributes should
|
||||
always be in code blocks using backticks (`` ` ``).
|
||||
|
||||
Sort the attributes in the table: first, required, then alphabetically.
|
||||
|
||||
```markdown
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-----|:---------|:------------|
|
||||
| Attribute | Type | Required | Description |
|
||||
| :------------- | :------------ | :--------------------- | :--------------------------------------------------- |
|
||||
| `user` | string | **{check-circle}** Yes | The GitLab username. |
|
||||
| `assignee_ids` | integer array | **{dotted-circle}** No | The IDs of the users to assign the issue to. |
|
||||
| `confidential` | boolean | **{dotted-circle}** No | Set an issue to be confidential. Default is `false`. |
|
||||
```
|
||||
|
||||
Rendered example:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:--------------------|
|
||||
| `user` | string | yes | The GitLab username. |
|
||||
| Attribute | Type | Required | Description |
|
||||
| :------------- | :------------ | :--------------------- | :--------------------------------------------------- |
|
||||
| `user` | string | **{check-circle}** Yes | The GitLab username. |
|
||||
| `assignee_ids` | integer array | **{dotted-circle}** No | The IDs of the users to assign the issue to. |
|
||||
| `confidential` | boolean | **{dotted-circle}** No | Set an issue to be confidential. Default is `false`. |
|
||||
|
||||
## cURL commands
|
||||
|
||||
|
@ -101,12 +108,12 @@ Rendered example:
|
|||
- Prefer to use examples using the personal access token and don't pass data of
|
||||
username and password.
|
||||
|
||||
| Methods | Description |
|
||||
|:------------------------------------------- |:------------------------------------------------------|
|
||||
| Methods | Description |
|
||||
| :---------------------------------------------- | :----------------------------------------------------- |
|
||||
| `--header "PRIVATE-TOKEN: <your_access_token>"` | Use this method as is, whenever authentication needed. |
|
||||
| `--request POST` | Use this method when creating new objects |
|
||||
| `--request PUT` | Use this method when updating existing objects |
|
||||
| `--request DELETE` | Use this method when removing existing objects |
|
||||
| `--request POST` | Use this method when creating new objects |
|
||||
| `--request PUT` | Use this method when updating existing objects |
|
||||
| `--request DELETE` | Use this method when removing existing objects |
|
||||
|
||||
## cURL Examples
|
||||
|
||||
|
|
|
@ -1,829 +1,9 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Source Code
|
||||
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/#assignments"
|
||||
type: reference, api
|
||||
redirect_to: 'internal_api/index.md'
|
||||
remove_date: '2022-02-09'
|
||||
---
|
||||
|
||||
# Internal API **(FREE)**
|
||||
This document was moved to [another location](internal_api/index.md).
|
||||
|
||||
The internal API is used by different GitLab components, it can not be
|
||||
used by other consumers. This documentation is intended for people
|
||||
working on the GitLab codebase.
|
||||
|
||||
This documentation does not yet include the internal API used by
|
||||
GitLab Pages.
|
||||
|
||||
## Adding new endpoints
|
||||
|
||||
API endpoints should be externally accessible by default, with proper authentication and authorization.
|
||||
Before adding a new internal endpoint, consider if the API would potentially be
|
||||
useful to the wider GitLab community and can be made externally accessible.
|
||||
|
||||
One reason we might favor internal API endpoints sometimes is when using such an endpoint requires
|
||||
internal data that external actors can not have. For example, in the internal Pages API we might use
|
||||
a secret token that identifies a request as internal or sign a request with a public key that is
|
||||
not available to a wider community.
|
||||
|
||||
Another reason to separate something into an internal API is when request to such API endpoint
|
||||
should never go through an edge (public) load balancer. This way we can configure different rate
|
||||
limiting rules and policies around how the endpoint is being accessed, because we know that only
|
||||
internal requests can be made to that endpoint going through an internal load balancer.
|
||||
|
||||
## Authentication
|
||||
|
||||
These methods are all authenticated using a shared secret. This secret
|
||||
is stored in a file at the path configured in `config/gitlab.yml` by
|
||||
default this is in the root of the rails app named
|
||||
`.gitlab_shell_secret`
|
||||
|
||||
To authenticate using that token, clients read the contents of that
|
||||
file, and include the token Base64 encoded in a `secret_token` parameter
|
||||
or in the `Gitlab-Shared-Secret` header.
|
||||
|
||||
NOTE:
|
||||
The internal API used by GitLab Pages, and GitLab Kubernetes Agent Server (`kas`) uses JSON Web Token (JWT)
|
||||
authentication, which is different from GitLab Shell.
|
||||
|
||||
## Git Authentication
|
||||
|
||||
This is called by [Gitaly](https://gitlab.com/gitlab-org/gitaly) and
|
||||
[GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) to check access to a
|
||||
repository.
|
||||
|
||||
- **When called from GitLab Shell**: No changes are passed, and the internal
|
||||
API replies with the information needed to pass the request on to Gitaly.
|
||||
- **When called from Gitaly in a `pre-receive` hook**: The changes are passed
|
||||
and validated to determine if the push is allowed.
|
||||
|
||||
Calls are limited to 50 seconds each.
|
||||
|
||||
```plaintext
|
||||
POST /internal/allowed
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
|
||||
| `username` | string | no | Username from the certificate used to connect to GitLab Shell |
|
||||
| `project` | string | no (if `gl_repository` is passed) | Path to the project |
|
||||
| `gl_repository` | string | no (if `project` is passed) | Repository identifier, such as `project-7` |
|
||||
| `protocol` | string | yes | SSH when called from GitLab Shell, HTTP or SSH when called from Gitaly |
|
||||
| `action` | string | yes | Git command being run (`git-upload-pack`, `git-receive-pack`, `git-upload-archive`) |
|
||||
| `changes` | string | yes | `<oldrev> <newrev> <refname>` when called from Gitaly, the magic string `_any` when called from GitLab Shell |
|
||||
| `check_ip` | string | no | IP address from which call to GitLab Shell was made |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
|
||||
--data "key_id=11&project=gnuwget/wget2&action=git-upload-pack&protocol=ssh" \
|
||||
"http://localhost:3001/api/v4/internal/allowed"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"gl_repository": "project-3",
|
||||
"gl_project_path": "gnuwget/wget2",
|
||||
"gl_id": "user-1",
|
||||
"gl_username": "root",
|
||||
"git_config_options": [],
|
||||
"gitaly": {
|
||||
"repository": {
|
||||
"storage_name": "default",
|
||||
"relative_path": "@hashed/4e/07/4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce.git",
|
||||
"git_object_directory": "",
|
||||
"git_alternate_object_directories": [],
|
||||
"gl_repository": "project-3",
|
||||
"gl_project_path": "gnuwget/wget2"
|
||||
},
|
||||
"address": "unix:/Users/bvl/repos/gitlab/gitaly.socket",
|
||||
"token": null
|
||||
},
|
||||
"gl_console_messages": []
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- Gitaly
|
||||
- GitLab Shell
|
||||
|
||||
## LFS Authentication
|
||||
|
||||
This is the endpoint that gets called from GitLab Shell to provide
|
||||
information for LFS clients when the repository is accessed over SSH.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
|
||||
| `username`| string | no | Username from the certificate used to connect to GitLab Shell |
|
||||
| `project` | string | no | Path to the project |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
|
||||
--data "key_id=11&project=gnuwget/wget2" "http://localhost:3001/api/v4/internal/lfs_authenticate"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "root",
|
||||
"lfs_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImFjdG9yIjoicm9vdCJ9LCJqdGkiOiIyYWJhZDcxZC0xNDFlLTQ2NGUtOTZlMi1mODllYWRiMGVmZTYiLCJpYXQiOjE1NzAxMTc2NzYsIm5iZiI6MTU3MDExNzY3MSwiZXhwIjoxNTcwMTE5NDc2fQ.g7atlBw1QMY7QEBVPE0LZ8ZlKtaRzaMRmNn41r2YITM",
|
||||
"repository_http_path": "http://localhost:3001/gnuwget/wget2.git",
|
||||
"expires_in": 1800
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Authorized Keys Check
|
||||
|
||||
This endpoint is called by the GitLab Shell authorized keys
|
||||
check. Which is called by OpenSSH for [fast SSH key
|
||||
lookup](../administration/operations/fast_ssh_key_lookup.md).
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key` | string | yes | SSH key as passed by OpenSSH to GitLab Shell |
|
||||
|
||||
```plaintext
|
||||
GET /internal/authorized_keys
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/authorized_keys?key=<key as passed by OpenSSH>"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 11,
|
||||
"title": "admin@example.com",
|
||||
"key": "ssh-rsa ...",
|
||||
"created_at": "2019-06-27T15:29:02.219Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Get user for user ID or key
|
||||
|
||||
This endpoint is used when a user performs `ssh git@gitlab.com`. It
|
||||
discovers the user associated with an SSH key.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
|
||||
| `username` | string | no | Username of the user being looked up, used by GitLab Shell when authenticating using a certificate |
|
||||
|
||||
```plaintext
|
||||
GET /internal/discover
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/discover?key_id=7"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Dede Eichmann",
|
||||
"username": "rubi"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Instance information
|
||||
|
||||
This gets some generic information about the instance. This is used
|
||||
by Geo nodes to get information about each other.
|
||||
|
||||
```plaintext
|
||||
GET /internal/check
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/check"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_version": "v4",
|
||||
"gitlab_version": "12.3.0-pre",
|
||||
"gitlab_rev": "d69c988e6a6",
|
||||
"redis": true
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Geo
|
||||
- GitLab Shell's `bin/check`
|
||||
- Gitaly
|
||||
|
||||
## Get new 2FA recovery codes using an SSH key
|
||||
|
||||
This is called from GitLab Shell and allows users to get new 2FA
|
||||
recovery codes based on their SSH key.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
|
||||
| `user_id` | integer | no | **Deprecated** User_id for which to generate new recovery codes |
|
||||
|
||||
```plaintext
|
||||
GET /internal/two_factor_recovery_codes
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "key_id=7" "http://localhost:3001/api/v4/internal/two_factor_recovery_codes"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"recovery_codes": [
|
||||
"d93ee7037944afd5",
|
||||
"19d7b84862de93dd",
|
||||
"1e8c52169195bf71",
|
||||
"be50444dddb7ca84",
|
||||
"26048c77d161d5b7",
|
||||
"482d5c03d1628c47",
|
||||
"d2c695e309ce7679",
|
||||
"dfb4748afc4f12a7",
|
||||
"0e5f53d1399d7979",
|
||||
"af04d5622153b020"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Get new personal access-token
|
||||
|
||||
This is called from GitLab Shell and allows users to generate a new
|
||||
personal access token.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `name` | string | yes | The name of the new token |
|
||||
| `scopes` | string array | yes | The authorization scopes for the new token, these must be valid token scopes |
|
||||
| `expires_at` | string | no | The expiry date for the new token |
|
||||
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
|
||||
| `user_id` | integer | no | User ID for which to generate the new token |
|
||||
|
||||
```plaintext
|
||||
POST /internal/personal_access_token
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "user_id=29&name=mytokenname&scopes[]=read_user&scopes[]=read_repository&expires_at=2020-07-24" \
|
||||
"http://localhost:3001/api/v4/internal/personal_access_token"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"token": "Hf_79B288hRv_3-TSD1R",
|
||||
"scopes": ["read_user","read_repository"],
|
||||
"expires_at": "2020-07-24"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Incrementing counter on pre-receive
|
||||
|
||||
This is called from the Gitaly hooks increasing the reference counter
|
||||
for a push that might be accepted.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `gl_repository` | string | yes | repository identifier for the repository receiving the push |
|
||||
|
||||
```plaintext
|
||||
POST /internal/pre_receive
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "gl_repository=project-7" "http://localhost:3001/api/v4/internal/pre_receive"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"reference_counter_increased": true
|
||||
}
|
||||
```
|
||||
|
||||
## PostReceive
|
||||
|
||||
Called from Gitaly after a receiving a push. This triggers the
|
||||
`PostReceive`-worker in Sidekiq, processes the passed push options and
|
||||
builds the response including messages that need to be displayed to
|
||||
the user.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `identifier` | string | yes | `user-[id]` or `key-[id]` Identifying the user performing the push |
|
||||
| `gl_repository` | string | yes | identifier of the repository being pushed to |
|
||||
| `push_options` | string array | no | array of push options |
|
||||
| `changes` | string | no | refs to be updated in the push in the format `oldrev newrev refname\n`. |
|
||||
|
||||
```plaintext
|
||||
POST /internal/post_receive
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "gl_repository=project-7" --data "identifier=user-1" \
|
||||
--data "changes=0000000000000000000000000000000000000000 fd9e76b9136bdd9fe217061b497745792fe5a5ee gh-pages\n" \
|
||||
"http://localhost:3001/api/v4/internal/post_receive"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"message": "Hello from post-receive",
|
||||
"type": "alert"
|
||||
}
|
||||
],
|
||||
"reference_counter_decreased": true
|
||||
}
|
||||
```
|
||||
|
||||
## Kubernetes agent endpoints
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41045) in GitLab 13.4.
|
||||
> - This feature is not deployed on GitLab.com
|
||||
> - It's not recommended for production use.
|
||||
|
||||
The following endpoints are used by the GitLab Kubernetes Agent Server (`kas`)
|
||||
for various purposes.
|
||||
|
||||
These endpoints are all authenticated using JWT. The JWT secret is stored in a file
|
||||
specified in `config/gitlab.yml`. By default, the location is in the root of the
|
||||
GitLab Rails app in a file called `.gitlab_kas_secret`.
|
||||
|
||||
WARNING:
|
||||
The Kubernetes agent is under development and is not recommended for production use.
|
||||
|
||||
### Kubernetes agent information
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve agent
|
||||
information for the given agent token. This returns the Gitaly connection
|
||||
information for the agent's project in order for `kas` to fetch and update
|
||||
the agent's configuration.
|
||||
|
||||
```plaintext
|
||||
GET /internal/kubernetes/agent_info
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/agent_info"
|
||||
```
|
||||
|
||||
### Kubernetes agent project information
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve project
|
||||
information for the given agent token. This returns the Gitaly
|
||||
connection for the requested project. GitLab `kas` uses this to configure
|
||||
the agent to fetch Kubernetes resources from the project repository to
|
||||
sync.
|
||||
|
||||
Only public projects are supported. For private projects, the ability for the
|
||||
agent to be authorized is [not yet implemented](https://gitlab.com/gitlab-org/gitlab/-/issues/220912).
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../api/index.md#namespaced-path-encoding) |
|
||||
|
||||
```plaintext
|
||||
GET /internal/kubernetes/project_info
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/project_info?id=7"
|
||||
```
|
||||
|
||||
### Kubernetes agent usage metrics
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (`kas`) to increase the usage
|
||||
metric counters.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `gitops_sync_count` | integer| no | The number to increase the `gitops_sync_count` counter by |
|
||||
| `k8s_api_proxy_request_count` | integer| no | The number to increase the `k8s_api_proxy_request_count` counter by |
|
||||
|
||||
```plaintext
|
||||
POST /internal/kubernetes/usage_metrics
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Content-Type: application/json" \
|
||||
--data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
|
||||
```
|
||||
|
||||
### Kubernetes agent alert metrics
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (KAS) to save alerts derived from Cilium on Kubernetes
|
||||
Cluster.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `alert` | Hash | yes | Alerts detail. Same format as [3rd party alert](../operations/incident_management/integrations.md#customize-the-alert-payload-outside-of-gitlab). |
|
||||
|
||||
```plaintext
|
||||
POST internal/kubernetes/modules/cilium_alert
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
|
||||
--data '"{\"alert\":{\"title\":\"minimal\",\"message\":\"network problem\",\"evalMatches\":[{\"value\":1,\"metric\":\"Count\",\"tags\":{}}]}}"' \
|
||||
"http://localhost:3000/api/v4/internal/kubernetes/modules/cilium_alert"
|
||||
```
|
||||
|
||||
### Create Starboard vulnerability
|
||||
|
||||
Called from the GitLab Kubernetes Agent Server (`kas`) to create a security vulnerability
|
||||
from a Starboard vulnerability report. This request is idempotent. Multiple requests with the same data
|
||||
create a single vulnerability.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------------|:-------|:---------|:------------|
|
||||
| `vulnerability` | Hash | yes | Vulnerability data matching the security report schema [`vulnerability` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
|
||||
| `scanner` | Hash | yes | Scanner data matching the security report schema [`scanner` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
|
||||
|
||||
```plaintext
|
||||
PUT internal/kubernetes/modules/starboard_vulnerability
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
|
||||
--url "http://localhost:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability" \
|
||||
--data '{
|
||||
"vulnerability": {
|
||||
"name": "CVE-123-4567 in libc",
|
||||
"severity": "high",
|
||||
"confidence": "unknown",
|
||||
"location": {
|
||||
"kubernetes_resource": {
|
||||
"namespace": "production",
|
||||
"kind": "deployment",
|
||||
"name": "nginx",
|
||||
"container": "nginx"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "cve",
|
||||
"name": "CVE-123-4567",
|
||||
"value": "CVE-123-4567"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scanner": {
|
||||
"id": "starboard_trivy",
|
||||
"name": "Trivy (via Starboard Operator)",
|
||||
"vendor": "GitLab"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Subscriptions
|
||||
|
||||
The subscriptions endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
|
||||
in order to apply subscriptions including trials, and add-on purchases, for personal namespaces or top-level groups within GitLab.com.
|
||||
|
||||
### Creating a subscription
|
||||
|
||||
Use a POST to create a subscription.
|
||||
|
||||
```plaintext
|
||||
POST /namespaces/:id/gitlab_subscription
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `start_date` | date | yes | Start date of subscription |
|
||||
| `end_date` | date | no | End date of subscription |
|
||||
| `plan_code` | string | no | Subscription tier code |
|
||||
| `seats` | integer | no | Number of seats in subscription |
|
||||
| `max_seats_used` | integer | no | Highest number of active users in the last month |
|
||||
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
|
||||
| `trial` | boolean | no | Whether subscription is a trial |
|
||||
| `trial_starts_on` | date | no | Start date of trial |
|
||||
| `trial_ends_on` | date | no | End date of trial |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?start_date="2020-07-15"&plan="premium"&seats=10"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": {
|
||||
"code":"premium",
|
||||
"name":"premium",
|
||||
"trial":false,
|
||||
"auto_renew":null,
|
||||
"upgradable":false
|
||||
},
|
||||
"usage": {
|
||||
"seats_in_subscription":10,
|
||||
"seats_in_use":1,
|
||||
"max_seats_used":0,
|
||||
"seats_owed":0
|
||||
},
|
||||
"billing": {
|
||||
"subscription_start_date":"2020-07-15",
|
||||
"subscription_end_date":null,
|
||||
"trial_ends_on":null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Updating a subscription
|
||||
|
||||
Use a PUT command to update an existing subscription.
|
||||
|
||||
```plaintext
|
||||
PUT /namespaces/:id/gitlab_subscription
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `start_date` | date | no | Start date of subscription |
|
||||
| `end_date` | date | no | End date of subscription |
|
||||
| `plan_code` | string | no | Subscription tier code |
|
||||
| `seats` | integer | no | Number of seats in subscription |
|
||||
| `max_seats_used` | integer | no | Highest number of active users in the last month |
|
||||
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
|
||||
| `trial` | boolean | no | Whether subscription is a trial |
|
||||
| `trial_starts_on` | date | no | Start date of trial. Required if trial is true. |
|
||||
| `trial_ends_on` | date | no | End date of trial |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?max_seats_used=0"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": {
|
||||
"code":"premium",
|
||||
"name":"premium",
|
||||
"trial":false,
|
||||
"auto_renew":null,
|
||||
"upgradable":false
|
||||
},
|
||||
"usage": {
|
||||
"seats_in_subscription":80,
|
||||
"seats_in_use":82,
|
||||
"max_seats_used":0,
|
||||
"seats_owed":2
|
||||
},
|
||||
"billing": {
|
||||
"subscription_start_date":"2020-07-15",
|
||||
"subscription_end_date":"2021-07-15",
|
||||
"trial_ends_on":null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieving a subscription
|
||||
|
||||
Use a GET command to view an existing subscription.
|
||||
|
||||
```plaintext
|
||||
GET /namespaces/:id/gitlab_subscription
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": {
|
||||
"code":"premium",
|
||||
"name":"premium",
|
||||
"trial":false,
|
||||
"auto_renew":null,
|
||||
"upgradable":false
|
||||
},
|
||||
"usage": {
|
||||
"seats_in_subscription":80,
|
||||
"seats_in_use":82,
|
||||
"max_seats_used":82,
|
||||
"seats_owed":2
|
||||
},
|
||||
"billing": {
|
||||
"subscription_start_date":"2020-07-15",
|
||||
"subscription_end_date":"2021-07-15",
|
||||
"trial_ends_on":null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- CustomersDot
|
||||
|
||||
## CI minute provisioning
|
||||
|
||||
The CI Minute endpoints are used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
|
||||
to apply additional packs of CI minutes, for personal namespaces or top-level groups within GitLab.com.
|
||||
|
||||
### Creating an additional pack
|
||||
|
||||
Use a POST to create additional packs.
|
||||
|
||||
```plaintext
|
||||
POST /namespaces/:id/minutes
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `packs` | array | yes | An array of purchased minutes packs |
|
||||
| `packs[expires_at]` | date | yes | Expiry date of the purchased pack|
|
||||
| `packs[number_of_minutes]` | integer | yes | Number of additional minutes |
|
||||
| `packs[purchase_xid]` | string | yes | The unique ID of the purchase |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--url "http://localhost:3000/api/v4/namespaces/123/minutes" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'PRIVATE-TOKEN: <admin access token>' \
|
||||
--data '{
|
||||
"packs": [
|
||||
{
|
||||
"number_of_minutes": 10000,
|
||||
"expires_at": "2022-01-01",
|
||||
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"namespace_id": 123,
|
||||
"expires_at": "2022-01-01",
|
||||
"number_of_minutes": 10000,
|
||||
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Moving additional packs
|
||||
|
||||
Use a PATCH to move additional packs from one namespace to another.
|
||||
|
||||
```plaintext
|
||||
PATCH /namespaces/:id/minutes/move/:target_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `id` | string | yes | The ID of the namespace to transfer packs from |
|
||||
| `target_id` | string | yes | The ID of the target namespace to transfer the packs to |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PATCH \
|
||||
--url "http://localhost:3000/api/v4/namespaces/123/minutes/move/321" \
|
||||
--header 'PRIVATE-TOKEN: <admin access token>'
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "202 Accepted"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- CustomersDot
|
||||
|
||||
## Upcoming reconciliations
|
||||
|
||||
The `upcoming_reconciliations` endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
|
||||
to update upcoming reconciliations for namespaces.
|
||||
|
||||
### Updating `upcoming_reconciliations`
|
||||
|
||||
Use a PUT command to update `upcoming_reconciliations`.
|
||||
|
||||
```plaintext
|
||||
PUT /internal/upcoming_reconciliations
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:-------------------|:-----------|:---------|:------------|
|
||||
| `upcoming_reconciliations` | array | yes | Array of upcoming reconciliations |
|
||||
|
||||
Each array element contains:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:-------------------|:-----------|:---------|:------------|
|
||||
| `namespace_id` | integer | yes | ID of the namespace to be reconciled |
|
||||
| `next_reconciliation_date` | date | yes | Date when next reconciliation will happen |
|
||||
| `display_alert_from` | date | yes | Start date to display alert of upcoming reconciliation |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <admin_access_token>" --header "Content-Type: application/json" \
|
||||
--data '{"upcoming_reconciliations": [{"namespace_id": 127, "next_reconciliation_date": "13 Jun 2021", "display_alert_from": "06 Jun 2021"}, {"namespace_id": 129, "next_reconciliation_date": "12 Jun 2021", "display_alert_from": "05 Jun 2021"}]}' \
|
||||
"https://gitlab.com/api/v4/internal/upcoming_reconciliations"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```plaintext
|
||||
200
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- CustomersDot
|
||||
<!-- This redirect file can be deleted after <YYYY-MM-DD>. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
|
||||
|
|
|
@ -0,0 +1,829 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Source Code
|
||||
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/#assignments"
|
||||
type: reference, api
|
||||
---
|
||||
|
||||
# Internal API **(FREE)**
|
||||
|
||||
The internal API is used by different GitLab components, it can not be
|
||||
used by other consumers. This documentation is intended for people
|
||||
working on the GitLab codebase.
|
||||
|
||||
This documentation does not yet include the internal API used by
|
||||
GitLab Pages.
|
||||
|
||||
## Adding new endpoints
|
||||
|
||||
API endpoints should be externally accessible by default, with proper authentication and authorization.
|
||||
Before adding a new internal endpoint, consider if the API would potentially be
|
||||
useful to the wider GitLab community and can be made externally accessible.
|
||||
|
||||
One reason we might favor internal API endpoints sometimes is when using such an endpoint requires
|
||||
internal data that external actors can not have. For example, in the internal Pages API we might use
|
||||
a secret token that identifies a request as internal or sign a request with a public key that is
|
||||
not available to a wider community.
|
||||
|
||||
Another reason to separate something into an internal API is when request to such API endpoint
|
||||
should never go through an edge (public) load balancer. This way we can configure different rate
|
||||
limiting rules and policies around how the endpoint is being accessed, because we know that only
|
||||
internal requests can be made to that endpoint going through an internal load balancer.
|
||||
|
||||
## Authentication
|
||||
|
||||
These methods are all authenticated using a shared secret. This secret
|
||||
is stored in a file at the path configured in `config/gitlab.yml` by
|
||||
default this is in the root of the rails app named
|
||||
`.gitlab_shell_secret`
|
||||
|
||||
To authenticate using that token, clients read the contents of that
|
||||
file, and include the token Base64 encoded in a `secret_token` parameter
|
||||
or in the `Gitlab-Shared-Secret` header.
|
||||
|
||||
NOTE:
|
||||
The internal API used by GitLab Pages, and GitLab Kubernetes Agent Server (`kas`) uses JSON Web Token (JWT)
|
||||
authentication, which is different from GitLab Shell.
|
||||
|
||||
## Git Authentication
|
||||
|
||||
This is called by [Gitaly](https://gitlab.com/gitlab-org/gitaly) and
|
||||
[GitLab Shell](https://gitlab.com/gitlab-org/gitlab-shell) to check access to a
|
||||
repository.
|
||||
|
||||
- **When called from GitLab Shell**: No changes are passed, and the internal
|
||||
API replies with the information needed to pass the request on to Gitaly.
|
||||
- **When called from Gitaly in a `pre-receive` hook**: The changes are passed
|
||||
and validated to determine if the push is allowed.
|
||||
|
||||
Calls are limited to 50 seconds each.
|
||||
|
||||
```plaintext
|
||||
POST /internal/allowed
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
|
||||
| `username` | string | no | Username from the certificate used to connect to GitLab Shell |
|
||||
| `project` | string | no (if `gl_repository` is passed) | Path to the project |
|
||||
| `gl_repository` | string | no (if `project` is passed) | Repository identifier, such as `project-7` |
|
||||
| `protocol` | string | yes | SSH when called from GitLab Shell, HTTP or SSH when called from Gitaly |
|
||||
| `action` | string | yes | Git command being run (`git-upload-pack`, `git-receive-pack`, `git-upload-archive`) |
|
||||
| `changes` | string | yes | `<oldrev> <newrev> <refname>` when called from Gitaly, the magic string `_any` when called from GitLab Shell |
|
||||
| `check_ip` | string | no | IP address from which call to GitLab Shell was made |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
|
||||
--data "key_id=11&project=gnuwget/wget2&action=git-upload-pack&protocol=ssh" \
|
||||
"http://localhost:3001/api/v4/internal/allowed"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": true,
|
||||
"gl_repository": "project-3",
|
||||
"gl_project_path": "gnuwget/wget2",
|
||||
"gl_id": "user-1",
|
||||
"gl_username": "root",
|
||||
"git_config_options": [],
|
||||
"gitaly": {
|
||||
"repository": {
|
||||
"storage_name": "default",
|
||||
"relative_path": "@hashed/4e/07/4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce.git",
|
||||
"git_object_directory": "",
|
||||
"git_alternate_object_directories": [],
|
||||
"gl_repository": "project-3",
|
||||
"gl_project_path": "gnuwget/wget2"
|
||||
},
|
||||
"address": "unix:/Users/bvl/repos/gitlab/gitaly.socket",
|
||||
"token": null
|
||||
},
|
||||
"gl_console_messages": []
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- Gitaly
|
||||
- GitLab Shell
|
||||
|
||||
## LFS Authentication
|
||||
|
||||
This is the endpoint that gets called from GitLab Shell to provide
|
||||
information for LFS clients when the repository is accessed over SSH.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | string | no | ID of the SSH-key used to connect to GitLab Shell |
|
||||
| `username`| string | no | Username from the certificate used to connect to GitLab Shell |
|
||||
| `project` | string | no | Path to the project |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded token>" \
|
||||
--data "key_id=11&project=gnuwget/wget2" "http://localhost:3001/api/v4/internal/lfs_authenticate"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "root",
|
||||
"lfs_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImFjdG9yIjoicm9vdCJ9LCJqdGkiOiIyYWJhZDcxZC0xNDFlLTQ2NGUtOTZlMi1mODllYWRiMGVmZTYiLCJpYXQiOjE1NzAxMTc2NzYsIm5iZiI6MTU3MDExNzY3MSwiZXhwIjoxNTcwMTE5NDc2fQ.g7atlBw1QMY7QEBVPE0LZ8ZlKtaRzaMRmNn41r2YITM",
|
||||
"repository_http_path": "http://localhost:3001/gnuwget/wget2.git",
|
||||
"expires_in": 1800
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Authorized Keys Check
|
||||
|
||||
This endpoint is called by the GitLab Shell authorized keys
|
||||
check. Which is called by OpenSSH for [fast SSH key
|
||||
lookup](../../administration/operations/fast_ssh_key_lookup.md).
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key` | string | yes | SSH key as passed by OpenSSH to GitLab Shell |
|
||||
|
||||
```plaintext
|
||||
GET /internal/authorized_keys
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/authorized_keys?key=<key as passed by OpenSSH>"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 11,
|
||||
"title": "admin@example.com",
|
||||
"key": "ssh-rsa ...",
|
||||
"created_at": "2019-06-27T15:29:02.219Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Get user for user ID or key
|
||||
|
||||
This endpoint is used when a user performs `ssh git@gitlab.com`. It
|
||||
discovers the user associated with an SSH key.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
|
||||
| `username` | string | no | Username of the user being looked up, used by GitLab Shell when authenticating using a certificate |
|
||||
|
||||
```plaintext
|
||||
GET /internal/discover
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/discover?key_id=7"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Dede Eichmann",
|
||||
"username": "rubi"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Instance information
|
||||
|
||||
This gets some generic information about the instance. This is used
|
||||
by Geo nodes to get information about each other.
|
||||
|
||||
```plaintext
|
||||
GET /internal/check
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Shared-Secret: <Base64 encoded secret>" "http://localhost:3001/api/v4/internal/check"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_version": "v4",
|
||||
"gitlab_version": "12.3.0-pre",
|
||||
"gitlab_rev": "d69c988e6a6",
|
||||
"redis": true
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Geo
|
||||
- GitLab Shell's `bin/check`
|
||||
- Gitaly
|
||||
|
||||
## Get new 2FA recovery codes using an SSH key
|
||||
|
||||
This is called from GitLab Shell and allows users to get new 2FA
|
||||
recovery codes based on their SSH key.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
|
||||
| `user_id` | integer | no | **Deprecated** User_id for which to generate new recovery codes |
|
||||
|
||||
```plaintext
|
||||
GET /internal/two_factor_recovery_codes
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "key_id=7" "http://localhost:3001/api/v4/internal/two_factor_recovery_codes"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"recovery_codes": [
|
||||
"d93ee7037944afd5",
|
||||
"19d7b84862de93dd",
|
||||
"1e8c52169195bf71",
|
||||
"be50444dddb7ca84",
|
||||
"26048c77d161d5b7",
|
||||
"482d5c03d1628c47",
|
||||
"d2c695e309ce7679",
|
||||
"dfb4748afc4f12a7",
|
||||
"0e5f53d1399d7979",
|
||||
"af04d5622153b020"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Get new personal access-token
|
||||
|
||||
This is called from GitLab Shell and allows users to generate a new
|
||||
personal access token.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `name` | string | yes | The name of the new token |
|
||||
| `scopes` | string array | yes | The authorization scopes for the new token, these must be valid token scopes |
|
||||
| `expires_at` | string | no | The expiry date for the new token |
|
||||
| `key_id` | integer | no | The ID of the SSH key used as found in the authorized-keys file or through the `/authorized_keys` check |
|
||||
| `user_id` | integer | no | User ID for which to generate the new token |
|
||||
|
||||
```plaintext
|
||||
POST /internal/personal_access_token
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "user_id=29&name=mytokenname&scopes[]=read_user&scopes[]=read_repository&expires_at=2020-07-24" \
|
||||
"http://localhost:3001/api/v4/internal/personal_access_token"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"token": "Hf_79B288hRv_3-TSD1R",
|
||||
"scopes": ["read_user","read_repository"],
|
||||
"expires_at": "2020-07-24"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- GitLab Shell
|
||||
|
||||
## Incrementing counter on pre-receive
|
||||
|
||||
This is called from the Gitaly hooks increasing the reference counter
|
||||
for a push that might be accepted.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `gl_repository` | string | yes | repository identifier for the repository receiving the push |
|
||||
|
||||
```plaintext
|
||||
POST /internal/pre_receive
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "gl_repository=project-7" "http://localhost:3001/api/v4/internal/pre_receive"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"reference_counter_increased": true
|
||||
}
|
||||
```
|
||||
|
||||
## PostReceive
|
||||
|
||||
Called from Gitaly after a receiving a push. This triggers the
|
||||
`PostReceive`-worker in Sidekiq, processes the passed push options and
|
||||
builds the response including messages that need to be displayed to
|
||||
the user.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `identifier` | string | yes | `user-[id]` or `key-[id]` Identifying the user performing the push |
|
||||
| `gl_repository` | string | yes | identifier of the repository being pushed to |
|
||||
| `push_options` | string array | no | array of push options |
|
||||
| `changes` | string | no | refs to be updated in the push in the format `oldrev newrev refname\n`. |
|
||||
|
||||
```plaintext
|
||||
POST /internal/post_receive
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Shared-Secret: <Base64 encoded secret>" \
|
||||
--data "gl_repository=project-7" --data "identifier=user-1" \
|
||||
--data "changes=0000000000000000000000000000000000000000 fd9e76b9136bdd9fe217061b497745792fe5a5ee gh-pages\n" \
|
||||
"http://localhost:3001/api/v4/internal/post_receive"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"messages": [
|
||||
{
|
||||
"message": "Hello from post-receive",
|
||||
"type": "alert"
|
||||
}
|
||||
],
|
||||
"reference_counter_decreased": true
|
||||
}
|
||||
```
|
||||
|
||||
## Kubernetes agent endpoints
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41045) in GitLab 13.4.
|
||||
> - This feature is not deployed on GitLab.com
|
||||
> - It's not recommended for production use.
|
||||
|
||||
The following endpoints are used by the GitLab Kubernetes Agent Server (`kas`)
|
||||
for various purposes.
|
||||
|
||||
These endpoints are all authenticated using JWT. The JWT secret is stored in a file
|
||||
specified in `config/gitlab.yml`. By default, the location is in the root of the
|
||||
GitLab Rails app in a file called `.gitlab_kas_secret`.
|
||||
|
||||
WARNING:
|
||||
The Kubernetes agent is under development and is not recommended for production use.
|
||||
|
||||
### Kubernetes agent information
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve agent
|
||||
information for the given agent token. This returns the Gitaly connection
|
||||
information for the agent's project in order for `kas` to fetch and update
|
||||
the agent's configuration.
|
||||
|
||||
```plaintext
|
||||
GET /internal/kubernetes/agent_info
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/agent_info"
|
||||
```
|
||||
|
||||
### Kubernetes agent project information
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (`kas`) to retrieve project
|
||||
information for the given agent token. This returns the Gitaly
|
||||
connection for the requested project. GitLab `kas` uses this to configure
|
||||
the agent to fetch Kubernetes resources from the project repository to
|
||||
sync.
|
||||
|
||||
Only public projects are supported. For private projects, the ability for the
|
||||
agent to be authorized is [not yet implemented](https://gitlab.com/gitlab-org/gitlab/-/issues/220912).
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../../api/index.md#namespaced-path-encoding) |
|
||||
|
||||
```plaintext
|
||||
GET /internal/kubernetes/project_info
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request GET --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" "http://localhost:3000/api/v4/internal/kubernetes/project_info?id=7"
|
||||
```
|
||||
|
||||
### Kubernetes agent usage metrics
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (`kas`) to increase the usage
|
||||
metric counters.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `gitops_sync_count` | integer| no | The number to increase the `gitops_sync_count` counter by |
|
||||
| `k8s_api_proxy_request_count` | integer| no | The number to increase the `k8s_api_proxy_request_count` counter by |
|
||||
|
||||
```plaintext
|
||||
POST /internal/kubernetes/usage_metrics
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" --header "Content-Type: application/json" \
|
||||
--data '{"gitops_sync_count":1}' "http://localhost:3000/api/v4/internal/kubernetes/usage_metrics"
|
||||
```
|
||||
|
||||
### Kubernetes agent alert metrics
|
||||
|
||||
Called from GitLab Kubernetes Agent Server (KAS) to save alerts derived from Cilium on Kubernetes
|
||||
Cluster.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------|:-------|:---------|:------------|
|
||||
| `alert` | Hash | yes | Alerts detail. Same format as [3rd party alert](../../operations/incident_management/integrations.md#customize-the-alert-payload-outside-of-gitlab). |
|
||||
|
||||
```plaintext
|
||||
POST internal/kubernetes/modules/cilium_alert
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
|
||||
--data '"{\"alert\":{\"title\":\"minimal\",\"message\":\"network problem\",\"evalMatches\":[{\"value\":1,\"metric\":\"Count\",\"tags\":{}}]}}"' \
|
||||
"http://localhost:3000/api/v4/internal/kubernetes/modules/cilium_alert"
|
||||
```
|
||||
|
||||
### Create Starboard vulnerability
|
||||
|
||||
Called from the GitLab Kubernetes Agent Server (`kas`) to create a security vulnerability
|
||||
from a Starboard vulnerability report. This request is idempotent. Multiple requests with the same data
|
||||
create a single vulnerability.
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:----------------|:-------|:---------|:------------|
|
||||
| `vulnerability` | Hash | yes | Vulnerability data matching the security report schema [`vulnerability` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
|
||||
| `scanner` | Hash | yes | Scanner data matching the security report schema [`scanner` field](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/src/security-report-format.json). |
|
||||
|
||||
```plaintext
|
||||
PUT internal/kubernetes/modules/starboard_vulnerability
|
||||
```
|
||||
|
||||
Example Request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "Gitlab-Kas-Api-Request: <JWT token>" \
|
||||
--header "Authorization: Bearer <agent token>" --header "Content-Type: application/json" \
|
||||
--url "http://localhost:3000/api/v4/internal/kubernetes/modules/starboard_vulnerability" \
|
||||
--data '{
|
||||
"vulnerability": {
|
||||
"name": "CVE-123-4567 in libc",
|
||||
"severity": "high",
|
||||
"confidence": "unknown",
|
||||
"location": {
|
||||
"kubernetes_resource": {
|
||||
"namespace": "production",
|
||||
"kind": "deployment",
|
||||
"name": "nginx",
|
||||
"container": "nginx"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "cve",
|
||||
"name": "CVE-123-4567",
|
||||
"value": "CVE-123-4567"
|
||||
}
|
||||
]
|
||||
},
|
||||
"scanner": {
|
||||
"id": "starboard_trivy",
|
||||
"name": "Trivy (via Starboard Operator)",
|
||||
"vendor": "GitLab"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## Subscriptions
|
||||
|
||||
The subscriptions endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
|
||||
in order to apply subscriptions including trials, and add-on purchases, for personal namespaces or top-level groups within GitLab.com.
|
||||
|
||||
### Creating a subscription
|
||||
|
||||
Use a POST to create a subscription.
|
||||
|
||||
```plaintext
|
||||
POST /namespaces/:id/gitlab_subscription
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `start_date` | date | yes | Start date of subscription |
|
||||
| `end_date` | date | no | End date of subscription |
|
||||
| `plan_code` | string | no | Subscription tier code |
|
||||
| `seats` | integer | no | Number of seats in subscription |
|
||||
| `max_seats_used` | integer | no | Highest number of active users in the last month |
|
||||
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
|
||||
| `trial` | boolean | no | Whether subscription is a trial |
|
||||
| `trial_starts_on` | date | no | Start date of trial |
|
||||
| `trial_ends_on` | date | no | End date of trial |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?start_date="2020-07-15"&plan="premium"&seats=10"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": {
|
||||
"code":"premium",
|
||||
"name":"premium",
|
||||
"trial":false,
|
||||
"auto_renew":null,
|
||||
"upgradable":false
|
||||
},
|
||||
"usage": {
|
||||
"seats_in_subscription":10,
|
||||
"seats_in_use":1,
|
||||
"max_seats_used":0,
|
||||
"seats_owed":0
|
||||
},
|
||||
"billing": {
|
||||
"subscription_start_date":"2020-07-15",
|
||||
"subscription_end_date":null,
|
||||
"trial_ends_on":null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Updating a subscription
|
||||
|
||||
Use a PUT command to update an existing subscription.
|
||||
|
||||
```plaintext
|
||||
PUT /namespaces/:id/gitlab_subscription
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `start_date` | date | no | Start date of subscription |
|
||||
| `end_date` | date | no | End date of subscription |
|
||||
| `plan_code` | string | no | Subscription tier code |
|
||||
| `seats` | integer | no | Number of seats in subscription |
|
||||
| `max_seats_used` | integer | no | Highest number of active users in the last month |
|
||||
| `auto_renew` | boolean | no | Whether subscription auto-renews on end date |
|
||||
| `trial` | boolean | no | Whether subscription is a trial |
|
||||
| `trial_starts_on` | date | no | Start date of trial. Required if trial is true. |
|
||||
| `trial_ends_on` | date | no | End date of trial |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription?max_seats_used=0"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": {
|
||||
"code":"premium",
|
||||
"name":"premium",
|
||||
"trial":false,
|
||||
"auto_renew":null,
|
||||
"upgradable":false
|
||||
},
|
||||
"usage": {
|
||||
"seats_in_subscription":80,
|
||||
"seats_in_use":82,
|
||||
"max_seats_used":0,
|
||||
"seats_owed":2
|
||||
},
|
||||
"billing": {
|
||||
"subscription_start_date":"2020-07-15",
|
||||
"subscription_end_date":"2021-07-15",
|
||||
"trial_ends_on":null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieving a subscription
|
||||
|
||||
Use a GET command to view an existing subscription.
|
||||
|
||||
```plaintext
|
||||
GET /namespaces/:id/gitlab_subscription
|
||||
```
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "TOKEN: <admin_access_token>" "https://gitlab.com/api/v4/namespaces/1234/gitlab_subscription"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": {
|
||||
"code":"premium",
|
||||
"name":"premium",
|
||||
"trial":false,
|
||||
"auto_renew":null,
|
||||
"upgradable":false
|
||||
},
|
||||
"usage": {
|
||||
"seats_in_subscription":80,
|
||||
"seats_in_use":82,
|
||||
"max_seats_used":82,
|
||||
"seats_owed":2
|
||||
},
|
||||
"billing": {
|
||||
"subscription_start_date":"2020-07-15",
|
||||
"subscription_end_date":"2021-07-15",
|
||||
"trial_ends_on":null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- CustomersDot
|
||||
|
||||
## CI minute provisioning
|
||||
|
||||
The CI Minute endpoints are used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
|
||||
to apply additional packs of CI minutes, for personal namespaces or top-level groups within GitLab.com.
|
||||
|
||||
### Creating an additional pack
|
||||
|
||||
Use a POST to create additional packs.
|
||||
|
||||
```plaintext
|
||||
POST /namespaces/:id/minutes
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `packs` | array | yes | An array of purchased minutes packs |
|
||||
| `packs[expires_at]` | date | yes | Expiry date of the purchased pack|
|
||||
| `packs[number_of_minutes]` | integer | yes | Number of additional minutes |
|
||||
| `packs[purchase_xid]` | string | yes | The unique ID of the purchase |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--url "http://localhost:3000/api/v4/namespaces/123/minutes" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'PRIVATE-TOKEN: <admin access token>' \
|
||||
--data '{
|
||||
"packs": [
|
||||
{
|
||||
"number_of_minutes": 10000,
|
||||
"expires_at": "2022-01-01",
|
||||
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"namespace_id": 123,
|
||||
"expires_at": "2022-01-01",
|
||||
"number_of_minutes": 10000,
|
||||
"purchase_xid": "46952fe69bebc1a4de10b2b4ff439d0c"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Moving additional packs
|
||||
|
||||
Use a PATCH to move additional packs from one namespace to another.
|
||||
|
||||
```plaintext
|
||||
PATCH /namespaces/:id/minutes/move/:target_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:------------|:--------|:---------|:------------|
|
||||
| `id` | string | yes | The ID of the namespace to transfer packs from |
|
||||
| `target_id` | string | yes | The ID of the target namespace to transfer the packs to |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PATCH \
|
||||
--url "http://localhost:3000/api/v4/namespaces/123/minutes/move/321" \
|
||||
--header 'PRIVATE-TOKEN: <admin access token>'
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "202 Accepted"
|
||||
}
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- CustomersDot
|
||||
|
||||
## Upcoming reconciliations
|
||||
|
||||
The `upcoming_reconciliations` endpoint is used by [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) (`customers.gitlab.com`)
|
||||
to update upcoming reconciliations for namespaces.
|
||||
|
||||
### Updating `upcoming_reconciliations`
|
||||
|
||||
Use a PUT command to update `upcoming_reconciliations`.
|
||||
|
||||
```plaintext
|
||||
PUT /internal/upcoming_reconciliations
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:-------------------|:-----------|:---------|:------------|
|
||||
| `upcoming_reconciliations` | array | yes | Array of upcoming reconciliations |
|
||||
|
||||
Each array element contains:
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|:-------------------|:-----------|:---------|:------------|
|
||||
| `namespace_id` | integer | yes | ID of the namespace to be reconciled |
|
||||
| `next_reconciliation_date` | date | yes | Date when next reconciliation will happen |
|
||||
| `display_alert_from` | date | yes | Start date to display alert of upcoming reconciliation |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --header "PRIVATE-TOKEN: <admin_access_token>" --header "Content-Type: application/json" \
|
||||
--data '{"upcoming_reconciliations": [{"namespace_id": 127, "next_reconciliation_date": "13 Jun 2021", "display_alert_from": "06 Jun 2021"}, {"namespace_id": 129, "next_reconciliation_date": "12 Jun 2021", "display_alert_from": "05 Jun 2021"}]}' \
|
||||
"https://gitlab.com/api/v4/internal/upcoming_reconciliations"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```plaintext
|
||||
200
|
||||
```
|
||||
|
||||
### Known consumers
|
||||
|
||||
- CustomersDot
|
|
@ -13,7 +13,7 @@ GitLab Maintenance Mode **only** blocks writes from HTTP and SSH requests at the
|
|||
|
||||
- [the read-only database method](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/lib/ee/gitlab/database.rb#L13), which toggles special behavior when we are not allowed to write to the database. [Search the codebase for `Gitlab::Database.read_only?`.](https://gitlab.com/search?search=Gitlab%3A%3ADatabase.read_only%3F&group_id=9970&project_id=278964&scope=blobs&search_code=false&snippets=false&repository_ref=)
|
||||
- [the read-only middleware](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/middleware/read_only/controller.rb), where HTTP requests that cause database writes are blocked, unless explicitly allowed.
|
||||
- [Git push access via SSH is denied](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/lib/ee/gitlab/git_access.rb#L13) by returning 401 when `gitlab-shell` POSTs to [`/internal/allowed`](internal_api.md) to [check if access is allowed](internal_api.md#git-authentication).
|
||||
- [Git push access via SSH is denied](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/lib/ee/gitlab/git_access.rb#L13) by returning 401 when `gitlab-shell` POSTs to [`/internal/allowed`](internal_api/index.md) to [check if access is allowed](internal_api/index.md#git-authentication).
|
||||
- [Container registry authentication service](https://gitlab.com/gitlab-org/gitlab/-/blob/2425e9de50c678413ceaad6ee3bf66f42b7e228c/ee/app/services/ee/auth/container_registry_authentication_service.rb#L12), where updates to the container registry are blocked.
|
||||
|
||||
The database itself is not in read-only mode (except in a Geo secondary site) and can be written by sources other than the ones blocked.
|
||||
|
|
|
@ -48,7 +48,7 @@ application.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. Read [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
|
||||
1. Read [Configure initial settings](omniauth.md#configure-initial-settings)
|
||||
for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
|
|
@ -48,7 +48,7 @@ As you go through the Microsoft procedure, keep the following in mind:
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. Refer to [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
|
||||
1. Refer to [Configure initial settings](omniauth.md#configure-initial-settings)
|
||||
for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
@ -154,7 +154,7 @@ After you have created an application, follow the [Microsoft Quickstart document
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. Refer to [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
|
||||
1. Refer to [Configure initial settings](omniauth.md#configure-initial-settings)
|
||||
for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
|
|
@ -28,7 +28,7 @@ configure CAS for back-channel logout.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ Facebook. Facebook generates an app ID and secret key for you to use.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ When you create an OAuth 2 app in GitHub, you need the following information:
|
|||
- The URL of your GitLab instance, such as `https://gitlab.example.com`.
|
||||
- The authorization callback URL; in this case, `https://gitlab.example.com/users/auth`. Include the port number if your GitLab instance uses a non-default port.
|
||||
|
||||
See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
|
||||
After you have configured the GitHub provider, you need the following information. You must substitute that information in the GitLab configuration file in these next steps.
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ GitLab.com generates an application ID and secret key for you to use.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus installations authenticating against **GitLab.com**:
|
||||
|
|
|
@ -71,7 +71,7 @@ On your GitLab server:
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
1. Add the provider configuration:
|
||||
|
||||
For Omnibus GitLab:
|
||||
|
|
|
@ -100,7 +100,7 @@ to authenticate with Kerberos tokens.
|
|||
|
||||
#### Enable single sign-on
|
||||
|
||||
See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
|
||||
See [Configure initial settings](omniauth.md#configure-initial-settings)
|
||||
for initial settings to enable single sign-on and add Kerberos servers
|
||||
as an identity provider.
|
||||
|
||||
|
@ -137,7 +137,7 @@ with your Kerberos credentials.
|
|||
|
||||
The first time users sign in to GitLab with their Kerberos accounts,
|
||||
GitLab creates a matching account.
|
||||
Before you continue, review the [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) options in Omnibus and GitLab source. You must also include `kerberos`.
|
||||
Before you continue, review the [Configure initial settings](omniauth.md#configure-initial-settings) options in Omnibus and GitLab source. You must also include `kerberos`.
|
||||
|
||||
With that information at hand:
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ This strategy is designed to allow configuration of the simple OmniAuth SSO proc
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings
|
||||
|
||||
1. Add the provider-specific configuration for your provider, as [described in the gem's README](https://gitlab.com/satorix/omniauth-oauth2-generic#gitlab-config-example)
|
||||
|
||||
|
|
|
@ -6,25 +6,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
# OmniAuth **(FREE SELF)**
|
||||
|
||||
GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and
|
||||
other popular services. [OmniAuth](https://rubygems.org/gems/omniauth/) is a
|
||||
"generalized Rack framework for multiple-provider authentication" built on Ruby.
|
||||
Users can sign in to GitLab by using their credentials from Twitter, GitHub, and other popular services.
|
||||
[OmniAuth](https://rubygems.org/gems/omniauth/) is the Rack
|
||||
framework that GitLab uses to provide this authentication.
|
||||
|
||||
Configuring OmniAuth does not prevent standard GitLab authentication or LDAP
|
||||
(if configured) from continuing to work. Users can choose to sign in using any
|
||||
of the configured mechanisms.
|
||||
If you configure OmniAuth, users can continue to sign in using other
|
||||
mechanisms, including standard GitLab authentication or LDAP (if configured).
|
||||
|
||||
- [Initial OmniAuth Configuration](#initial-omniauth-configuration)
|
||||
- [Supported Providers](#supported-providers)
|
||||
- [Enable OmniAuth for an Existing User](#enable-omniauth-for-an-existing-user)
|
||||
- [OmniAuth configuration sample when using Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab/tree/master#omniauth-google-twitter-github-login)
|
||||
- [Enable or disable Sign In with an OmniAuth provider without disabling import sources](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources)
|
||||
## Supported providers
|
||||
|
||||
## Supported Providers
|
||||
|
||||
This is a list of the current supported OmniAuth providers. Before proceeding on
|
||||
each provider's documentation, make sure to first read this document as it
|
||||
contains some settings that are common for all providers.
|
||||
GitLab supports the following OmniAuth providers.
|
||||
|
||||
| Provider documentation | OmniAuth provider name |
|
||||
|---------------------------------------------------------------------|----------------------------|
|
||||
|
@ -50,100 +41,78 @@ contains some settings that are common for all providers.
|
|||
| [Shibboleth](saml.md) | `shibboleth` |
|
||||
| [Twitter](twitter.md) | `twitter` |
|
||||
|
||||
## Initial OmniAuth Configuration
|
||||
|
||||
The OmniAuth provider names from the table above are needed to configure a few
|
||||
global settings that are in common for all providers.
|
||||
## Configure initial settings
|
||||
|
||||
NOTE:
|
||||
Starting from GitLab 11.4, OmniAuth is enabled by default. If you're using an
|
||||
In GitLab 11.4 and later, OmniAuth is enabled by default. If you're using an
|
||||
earlier version, you must explicitly enable it.
|
||||
|
||||
- `allow_single_sign_on` allows you to specify the providers that automatically
|
||||
create a GitLab account. For example, if you wish to enable Azure (v2) and Google,
|
||||
in Omnibus, specify a list of provider names:
|
||||
Before you configure the OmniAuth provider,
|
||||
configure the settings that are common for all providers.
|
||||
|
||||
```ruby
|
||||
gitlab_rails['omniauth_allow_single_sign_on'] = ['azure_activedirectory_v2', 'google_oauth2']
|
||||
```
|
||||
|
||||
The value defaults to `false`. If `false` users must be created manually, or
|
||||
they can't sign in by using OmniAuth.
|
||||
|
||||
- `auto_link_ldap_user` can be used if you have [LDAP / ActiveDirectory](../administration/auth/ldap/index.md)
|
||||
integration enabled. It defaults to `false`. When enabled, users automatically
|
||||
created through an OmniAuth provider have their LDAP identity created in GitLab as well.
|
||||
- `block_auto_created_users` defaults to `true`. If `true`, auto created users will
|
||||
be blocked pending approval by an administrator before they are able to sign in.
|
||||
|
||||
NOTE:
|
||||
If you set `block_auto_created_users` to `false`, make sure to only
|
||||
define providers under `allow_single_sign_on` that you are able to control, like
|
||||
SAML, Shibboleth, Crowd, or Google. Otherwise, set it to `true`, or any user on
|
||||
the Internet can successfully sign in to your GitLab without
|
||||
administrative approval.
|
||||
|
||||
NOTE:
|
||||
`auto_link_ldap_user` requires the `uid` of the user to be the same in both LDAP
|
||||
and the OmniAuth provider.
|
||||
Setting | Description | Default value
|
||||
---------------------------|-------------|--------------
|
||||
`allow_single_sign_on` | Enables you to list the providers that automatically create a GitLab account. The provider names are available in the **OmniAuth provider name** column in the [supported providers table](#supported-providers). | The default is `false`. If `false`, users must be created manually, or they can't sign in using OmniAuth.
|
||||
`auto_link_ldap_user` | If enabled, creates an LDAP identity in GitLab for users that are created through an OmniAuth provider. You can enable this setting if you have the [LDAP (ActiveDirectory)](../administration/auth/ldap/index.md) integration enabled. Requires the `uid` of the user to be the same in both LDAP and the OmniAuth provider. | The default is `false`.
|
||||
`block_auto_created_users` | If enabled, blocks users that are automatically created from signing in until they are approved by an administrator. | The default is `true`. If you set the value to `false`, make sure you only define providers for `allow_single_sign_on` that you can control, like SAML, Shibboleth, Crowd, or Google. Otherwise, any user on the internet can sign in to GitLab without an administrator's approval.
|
||||
|
||||
To change these settings:
|
||||
|
||||
- **For Omnibus package**
|
||||
|
||||
Open the configuration file:
|
||||
1. Open the configuration file:
|
||||
|
||||
```shell
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
```
|
||||
```shell
|
||||
sudo editor /etc/gitlab/gitlab.rb
|
||||
```
|
||||
|
||||
and change:
|
||||
1. Update the following section:
|
||||
|
||||
```ruby
|
||||
# CAUTION!
|
||||
# This allows users to sign in without having a user account first. Define the allowed providers
|
||||
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
|
||||
# User accounts will be created automatically when authentication was successful.
|
||||
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'twitter']
|
||||
gitlab_rails['omniauth_auto_link_ldap_user'] = true
|
||||
gitlab_rails['omniauth_block_auto_created_users'] = true
|
||||
```
|
||||
```ruby
|
||||
# CAUTION!
|
||||
# This allows users to sign in without having a user account first. Define the allowed providers
|
||||
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
|
||||
# User accounts will be created automatically when authentication was successful.
|
||||
gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'twitter']
|
||||
gitlab_rails['omniauth_auto_link_ldap_user'] = true
|
||||
gitlab_rails['omniauth_block_auto_created_users'] = true
|
||||
```
|
||||
|
||||
- **For installations from source**
|
||||
|
||||
Open the configuration file:
|
||||
1. Open the configuration file:
|
||||
|
||||
```shell
|
||||
cd /home/git/gitlab
|
||||
```shell
|
||||
cd /home/git/gitlab
|
||||
|
||||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
and change the following section:
|
||||
1. Update the following section:
|
||||
|
||||
```yaml
|
||||
## OmniAuth settings
|
||||
omniauth:
|
||||
# Allow sign-in by using Twitter, Google, etc. using OmniAuth providers
|
||||
# Versions prior to 11.4 require this to be set to true
|
||||
# enabled: true
|
||||
```yaml
|
||||
## OmniAuth settings
|
||||
omniauth:
|
||||
# Allow sign-in by using Twitter, Google, etc. using OmniAuth providers
|
||||
# Versions prior to 11.4 require this to be set to true
|
||||
# enabled: true
|
||||
|
||||
# CAUTION!
|
||||
# This allows users to sign in without having a user account first. Define the allowed providers
|
||||
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
|
||||
# User accounts will be created automatically when authentication was successful.
|
||||
allow_single_sign_on: ["saml", "twitter"]
|
||||
# CAUTION!
|
||||
# This allows users to sign in without having a user account first. Define the allowed providers
|
||||
# using an array, for example, ["saml", "twitter"], or as true/false to allow all providers or none.
|
||||
# User accounts will be created automatically when authentication was successful.
|
||||
allow_single_sign_on: ["saml", "twitter"]
|
||||
|
||||
auto_link_ldap_user: true
|
||||
auto_link_ldap_user: true
|
||||
|
||||
# Locks down those users until they have been cleared by the admin (default: true).
|
||||
block_auto_created_users: true
|
||||
```
|
||||
# Locks down those users until they have been cleared by the admin (default: true).
|
||||
block_auto_created_users: true
|
||||
```
|
||||
|
||||
Now we can choose one or more of the [Supported Providers](#supported-providers)
|
||||
listed above to continue the configuration process.
|
||||
After configuring these settings, you can configure
|
||||
your chosen [provider](#supported-providers).
|
||||
|
||||
## Enable OmniAuth for an Existing User
|
||||
## Enable OmniAuth for an existing user
|
||||
|
||||
Existing users can enable OmniAuth for specific providers after the account is
|
||||
created. For example, if the user originally signed in with LDAP, an OmniAuth
|
||||
|
@ -160,7 +129,7 @@ OmniAuth provider for an existing user.
|
|||
|
||||
The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on.
|
||||
|
||||
## Automatically Link Existing Users to OmniAuth Users
|
||||
## Link existing users to OmniAuth users
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36664) in GitLab 13.4.
|
||||
|
||||
|
@ -185,7 +154,7 @@ feature for both an **OpenID Connect provider** and a **Twitter OAuth provider**
|
|||
auto_link_user: ["openid_connect", "twitter"]
|
||||
```
|
||||
|
||||
## Configure OmniAuth Providers as External
|
||||
## Configure OmniAuth providers as external
|
||||
|
||||
You can define which OmniAuth providers you want to be `external`. Users
|
||||
creating accounts, or logging in by using these `external` providers cannot have
|
||||
|
@ -211,7 +180,7 @@ their accounts to be upgraded to full internal accounts.
|
|||
external_providers: ['twitter', 'google_oauth2']
|
||||
```
|
||||
|
||||
## Using Custom OmniAuth Providers
|
||||
## Use a custom OmniAuth provider
|
||||
|
||||
NOTE:
|
||||
The following information only applies for installations from source.
|
||||
|
@ -221,8 +190,6 @@ with a few providers pre-installed, such as LDAP, GitHub, and Twitter. You may a
|
|||
have to integrate with other authentication solutions. For
|
||||
these cases, you can use the OmniAuth provider.
|
||||
|
||||
### Steps
|
||||
|
||||
These steps are fairly general and you must figure out the exact details
|
||||
from the OmniAuth provider's documentation.
|
||||
|
||||
|
@ -252,7 +219,7 @@ from the OmniAuth provider's documentation.
|
|||
sudo service gitlab start
|
||||
```
|
||||
|
||||
### Examples
|
||||
### Custom OmniAuth provider examples
|
||||
|
||||
If you have successfully set up a provider that is not shipped with GitLab itself,
|
||||
please let us know.
|
||||
|
@ -260,7 +227,7 @@ please let us know.
|
|||
While we can't officially support every possible authentication mechanism out there,
|
||||
we'd like to at least help those with specific needs.
|
||||
|
||||
## Enable or disable Sign In with an OmniAuth provider without disabling import sources
|
||||
## Enable or disable sign-in with an OmniAuth provider without disabling import sources
|
||||
|
||||
Administrators are able to enable or disable **Sign In** by using some OmniAuth providers.
|
||||
|
||||
|
@ -276,7 +243,7 @@ To enable/disable an OmniAuth provider:
|
|||
|
||||
![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources_v13_10.png)
|
||||
|
||||
## Disabling OmniAuth
|
||||
## Disable OmniAuth
|
||||
|
||||
Starting from version 11.4 of GitLab, OmniAuth is enabled by default. This only
|
||||
has an effect if providers are configured and [enabled](#enable-or-disable-sign-in-with-an-omniauth-provider-without-disabling-import-sources).
|
||||
|
@ -318,7 +285,7 @@ When authenticating using LDAP, the user's name and email are always synced.
|
|||
sync_profile_attributes: ['email', 'location']
|
||||
```
|
||||
|
||||
## Bypassing two factor authentication
|
||||
## Bypass two-factor authentication
|
||||
|
||||
In GitLab 12.3 or later, users can sign in with specified providers _without_
|
||||
using two factor authentication.
|
||||
|
@ -341,7 +308,7 @@ configured only for providers which already have two factor authentication
|
|||
allow_bypass_two_factor: ['twitter', 'google_oauth2']
|
||||
```
|
||||
|
||||
## Automatically sign in with provider
|
||||
## Sign in with a provider automatically
|
||||
|
||||
You can add the `auto_sign_in_with_provider` setting to your GitLab
|
||||
configuration to redirect login requests to your OmniAuth provider for
|
||||
|
|
|
@ -48,7 +48,7 @@ To get the credentials (a pair of Client ID and Client Secret), you must [create
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ in your SAML IdP:
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
1. To allow your users to use SAML to sign up without having to manually create
|
||||
an account first, add the following values to your configuration:
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ Twitter. Twitter generates a client ID and secret key for you to use.
|
|||
sudo -u git -H editor config/gitlab.yml
|
||||
```
|
||||
|
||||
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
|
||||
1. See [Configure initial settings](omniauth.md#configure-initial-settings) for initial settings.
|
||||
|
||||
1. Add the provider configuration:
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ GitLab has several features which can help you manage the number of users:
|
|||
|
||||
- Enable the [**Require administrator approval for new sign ups**](../../user/admin_area/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups)
|
||||
option.
|
||||
- Enable `block_auto_created_users` for new sign-ups via [LDAP](../../administration/auth/ldap/index.md#basic-configuration-settings) or [OmniAuth](../../integration/omniauth.md#initial-omniauth-configuration).
|
||||
- Enable `block_auto_created_users` for new sign-ups via [LDAP](../../administration/auth/ldap/index.md#basic-configuration-settings) or [OmniAuth](../../integration/omniauth.md#configure-initial-settings).
|
||||
- Enable the [User cap](../../user/admin_area/settings/sign_up_restrictions.md#user-cap)
|
||||
option. **Available in GitLab 13.7 and later**.
|
||||
- [Disable new sign-ups](../../user/admin_area/settings/sign_up_restrictions.md), and instead manage new
|
||||
|
|
|
@ -145,6 +145,11 @@ To rebase from the UI:
|
|||
GitLab schedules a rebase of the feature branch against the default branch and
|
||||
executes it as soon as possible.
|
||||
|
||||
The user performing the rebase action is considered
|
||||
a user that added commits to the merge request. When the merge request approvals setting
|
||||
[**Prevent approvals by users who add commits**](../../user/project/merge_requests/approvals/settings.md#prevent-approvals-by-users-who-add-commits)
|
||||
is enabled, this setting prevents the user from also approving the merge request.
|
||||
|
||||
### Interactive rebase
|
||||
|
||||
You can use interactive rebase to modify commits. For example, amend a commit
|
||||
|
|
|
@ -17,7 +17,7 @@ pending approval state because an administrator has enabled any of the following
|
|||
|
||||
- [Require admin approval for new sign-ups](settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups) setting.
|
||||
- [User cap](settings/sign_up_restrictions.md#user-cap).
|
||||
- [Block auto-created users (OmniAuth)](../../integration/omniauth.md#initial-omniauth-configuration)
|
||||
- [Block auto-created users (OmniAuth)](../../integration/omniauth.md#configure-initial-settings)
|
||||
- [Block auto-created users (LDAP)](../../administration/auth/ldap/index.md#basic-configuration-settings)
|
||||
|
||||
When a user registers for an account while this setting is enabled:
|
||||
|
|
|
@ -48,7 +48,7 @@ automatically approved in a background job.
|
|||
NOTE:
|
||||
This setting doesn't apply to LDAP or OmniAuth users. To enforce approvals for new users
|
||||
signing up using OmniAuth or LDAP, set `block_auto_created_users` to `true` in the
|
||||
[OmniAuth configuration](../../../integration/omniauth.md#initial-omniauth-configuration) or
|
||||
[OmniAuth configuration](../../../integration/omniauth.md#configure-initial-settings) or
|
||||
[LDAP configuration](../../../administration/auth/ldap/index.md#basic-configuration-settings).
|
||||
|
||||
## Require email confirmation
|
||||
|
|
|
@ -10,8 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
> - [Deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
|
||||
|
||||
WARNING:
|
||||
This feature was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
|
||||
|
||||
The cluster management project was [deprecated](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) in GitLab 14.5.
|
||||
To manage cluster applications, use the [GitLab Kubernetes Agent](agent/index.md)
|
||||
with the [Cluster Management Project Template](management_project_template.md).
|
||||
|
||||
|
|
|
@ -25,10 +25,11 @@ To create an epic in the group you're in:
|
|||
- In an empty [roadmap](../roadmap/index.md), select **New epic**.
|
||||
|
||||
1. Enter a title.
|
||||
1. Optional. Enter a description.
|
||||
1. Optional. To make the epic confidential, select the [Confidentiality checkbox](#make-an-epic-confidential).
|
||||
1. Optional. Choose labels.
|
||||
1. Optional. Select a start and due date, or [inherit](#start-and-due-date-inheritance) them.
|
||||
1. Complete the fields.
|
||||
- Enter a description.
|
||||
- To [make the epic confidential](#make-an-epic-confidential), select the checkbox under **Confidentiality**.
|
||||
- Choose labels.
|
||||
- Select a start and due date, or [inherit](#start-and-due-date-inheritance) them.
|
||||
1. Select **Create epic**.
|
||||
|
||||
The newly created epic opens.
|
||||
|
@ -69,13 +70,13 @@ After you create an epic, you can edit the following details:
|
|||
|
||||
To edit an epic's title or description:
|
||||
|
||||
1. Select the **Edit title and description** **{pencil}** button.
|
||||
1. Select **Edit title and description** **{pencil}**.
|
||||
1. Make your changes.
|
||||
1. Select **Save changes**.
|
||||
|
||||
To edit an epic's start date, due date, or labels:
|
||||
|
||||
1. Select **Edit** next to each section in the epic sidebar.
|
||||
1. Next to each section in the right sidebar, select **Edit**.
|
||||
1. Select the dates or labels for your epic.
|
||||
|
||||
## Bulk edit epics
|
||||
|
@ -89,18 +90,21 @@ When bulk editing epics in a group, you can edit their labels.
|
|||
To update multiple epics at the same time:
|
||||
|
||||
1. In a group, go to **Epics > List**.
|
||||
1. Click **Edit epics**. A sidebar on the right-hand side of your screen appears with editable fields.
|
||||
1. Check the checkboxes next to each epic you want to edit.
|
||||
1. Select **Edit epics**. A sidebar on the right appears with editable fields.
|
||||
1. Select the checkboxes next to each epic you want to edit.
|
||||
1. Select the appropriate fields and their values from the sidebar.
|
||||
1. Click **Update all**.
|
||||
1. Select **Update all**.
|
||||
|
||||
## Delete an epic
|
||||
|
||||
NOTE:
|
||||
To delete an epic, you need to be an [Owner](../../permissions.md#group-members-permissions) of a group/subgroup.
|
||||
To delete an epic, you must be an [Owner](../../permissions.md#group-members-permissions) of a group
|
||||
or subgroup.
|
||||
|
||||
When editing the description of an epic, select the **Delete** button to delete the epic.
|
||||
A modal appears to confirm your action.
|
||||
To delete the epic:
|
||||
|
||||
1. Select **Edit title and description** **{pencil}**.
|
||||
1. Select **Delete**. A modal appears to confirm your action.
|
||||
|
||||
Deleting an epic releases all existing issues from their associated epic in the system.
|
||||
|
||||
|
@ -112,26 +116,26 @@ If you delete an epic, all its child epics and their descendants are deleted as
|
|||
Whenever you decide that there is no longer need for that epic,
|
||||
close the epic by:
|
||||
|
||||
- Selecting the **Close epic** button.
|
||||
- Selecting **Close epic**.
|
||||
|
||||
![close epic - button](img/button_close_epic.png)
|
||||
|
||||
- Using a [quick action](../../project/quick_actions.md).
|
||||
- Using the `/close` [quick action](../../project/quick_actions.md).
|
||||
|
||||
## Reopen a closed epic
|
||||
|
||||
You can reopen an epic that was closed by:
|
||||
|
||||
- Clicking the **Reopen epic** button.
|
||||
- Selecting **Reopen epic**.
|
||||
|
||||
![reopen epic - button](img/button_reopen_epic.png)
|
||||
|
||||
- Using a [quick action](../../project/quick_actions.md).
|
||||
- Using the `/reopen` [quick action](../../project/quick_actions.md).
|
||||
|
||||
## Go to an epic from an issue
|
||||
|
||||
If an issue belongs to an epic, you can navigate to the containing epic with the
|
||||
link in the issue sidebar.
|
||||
If an issue belongs to an epic, you can go to the parent epic with the
|
||||
link in the right sidebar.
|
||||
|
||||
![containing epic](img/containing_epic.png)
|
||||
|
||||
|
@ -156,13 +160,12 @@ than 1000. The cached value is rounded to thousands or millions and updated ever
|
|||
|
||||
## Search for an epic from epics list page
|
||||
|
||||
> - Introduced in GitLab 10.5.
|
||||
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/37081) from GitLab Ultimate to GitLab Premium in 12.8.
|
||||
> - Searching by the user's reaction emoji [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/325630) in GitLab 13.11.
|
||||
> - Sorting by epic titles [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331625) in GitLab 14.1.
|
||||
|
||||
You can search for an epic from the list of epics using filtered search bar (similar to
|
||||
that of issues and merge requests) based on following parameters:
|
||||
You can search for an epic from the list of epics using filtered search bar based on following
|
||||
parameters:
|
||||
|
||||
- Title or description
|
||||
- Author name / username
|
||||
|
@ -171,10 +174,13 @@ that of issues and merge requests) based on following parameters:
|
|||
|
||||
![epics search](img/epics_search_v13_11.png)
|
||||
|
||||
To search, go to the list of epics and select the field **Search or filter results**.
|
||||
It displays a dropdown menu, from which you can add an author. You can also enter plain
|
||||
text to search by epic title or description. When done, press <kbd>Enter</kbd> on your
|
||||
keyboard to filter the list.
|
||||
To search:
|
||||
|
||||
1. On the top bar, select **Menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Epics**.
|
||||
1. Select the field **Search or filter results**.
|
||||
1. From the dropdown menu, select the scope or enter plain text to search by epic title or description.
|
||||
1. Press <kbd>Enter</kbd> on your keyboard. The list is filtered.
|
||||
|
||||
You can also sort epics list by:
|
||||
|
||||
|
@ -198,7 +204,7 @@ You can reverse the default order and interact with the activity feed sorted by
|
|||
at the top. Your preference is saved via local storage and automatically applied to every epic and issue
|
||||
you view.
|
||||
|
||||
To change the activity sort order, click the **Oldest first** dropdown menu and select either oldest
|
||||
To change the activity sort order, select the **Oldest first** dropdown menu and select either oldest
|
||||
or newest items to be shown first.
|
||||
|
||||
![Issue activity sort order dropdown button](img/epic_activity_sort_order_v13_2.png)
|
||||
|
@ -219,8 +225,8 @@ to learn how to create a confidential merge request.
|
|||
|
||||
To make an epic confidential:
|
||||
|
||||
- **When creating an epic:** select the checkbox **Make this epic confidential**.
|
||||
- **In an existing epic:** in the epic's sidebar, select **Edit** next to **Confidentiality** then
|
||||
- **When creating an epic:** select the checkbox under **Confidentiality**.
|
||||
- **In an existing epic:** on the right sidebar, select **Edit** next to **Confidentiality**, and then
|
||||
select **Turn on**.
|
||||
|
||||
## Manage issues assigned to an epic
|
||||
|
@ -252,7 +258,7 @@ current parent.
|
|||
|
||||
To add a new issue to an epic:
|
||||
|
||||
1. On the epic's page, under **Epics and Issues**, select the **Add** dropdown button.
|
||||
1. On the epic's page, under **Epics and Issues**, select **Add**.
|
||||
1. Select **Add an existing issue**.
|
||||
1. Identify the issue to be added, using either of the following methods:
|
||||
- Paste the link of the issue.
|
||||
|
@ -271,7 +277,7 @@ while dividing work into smaller parts.
|
|||
|
||||
To create an issue from an epic:
|
||||
|
||||
1. On the epic's page, under **Epics and Issues**, select the **Add** dropdown button.
|
||||
1. On the epic's page, under **Epics and Issues**, select **Add**.
|
||||
1. Select **Add a new issue**.
|
||||
1. Under **Title**, enter the title for the new issue.
|
||||
1. From the **Project** dropdown, select the project in which the issue should be created.
|
||||
|
@ -284,7 +290,7 @@ After you remove an issue from an epic, the issue is no longer associated with t
|
|||
|
||||
To remove an issue from an epic:
|
||||
|
||||
1. Select the **Remove** (**{close}**) button next to the issue you want to remove.
|
||||
1. Next to the issue you want to remove, select **Remove** (**{close}**).
|
||||
The **Remove issue** warning appears.
|
||||
1. Select **Remove**.
|
||||
|
||||
|
@ -350,8 +356,6 @@ For more on epic templates, see [Epic Templates - Repeatable sets of issues](htt
|
|||
|
||||
## Multi-level child epics **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/8333) in GitLab 11.7.
|
||||
|
||||
You can add any epic that belongs to a group or subgroup of the parent epic's group.
|
||||
New child epics appear at the top of the list of epics in the **Epics and Issues** tab.
|
||||
|
||||
|
@ -363,7 +367,7 @@ Epics can contain multiple nested child epics, up to a total of seven levels dee
|
|||
|
||||
To add a child epic to an epic:
|
||||
|
||||
1. Select the **Add** dropdown button.
|
||||
1. Select **Add**.
|
||||
1. Select **Add a new epic**.
|
||||
1. Identify the epic to be added, using either of the following methods:
|
||||
- Paste the link of the epic.
|
||||
|
@ -403,5 +407,6 @@ To reorder child epics assigned to an epic:
|
|||
|
||||
To remove a child epic from a parent epic:
|
||||
|
||||
1. Select the <kbd>x</kbd> button in the parent epic's list of epics.
|
||||
1. Select **Remove** in the **Remove epic** warning message.
|
||||
1. Select **Remove** (**{close}**) in the parent epic's list of epics.
|
||||
The **Remove epic** warning appears.
|
||||
1. Select **Remove**.
|
||||
|
|
|
@ -25,6 +25,7 @@ The following aspects of a project are imported:
|
|||
- Pull request "merged by" information (GitLab.com & 13.7+)
|
||||
- Regular issue and pull request comments
|
||||
- [Git Large File Storage (LFS) Objects](../../../topics/git/lfs/index.md)
|
||||
- Pull request comments replies in discussions ([GitLab.com & 14.5+](https://gitlab.com/gitlab-org/gitlab/-/issues/336596))
|
||||
|
||||
References to pull requests and issues are preserved (GitLab.com & 8.7+), and
|
||||
each imported repository maintains visibility level unless that [visibility
|
||||
|
|
|
@ -38,6 +38,8 @@ Now, when you visit the merge request page, you can accept it
|
|||
If a fast-forward merge is not possible but a conflict free rebase is possible,
|
||||
a rebase button is offered.
|
||||
|
||||
The rebase action is also available as a [quick action command: `/rebase`](../../../topics/git/git_rebase.md#rebase-from-the-gitlab-ui).
|
||||
|
||||
![Fast forward merge request](img/ff_merge_rebase.png)
|
||||
|
||||
If the target branch is ahead of the source branch and a conflict free rebase is
|
||||
|
|
|
@ -7,14 +7,13 @@ type: index, reference
|
|||
|
||||
# Suggest changes **(FREE)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/18008) in GitLab 11.6.
|
||||
> - Custom commit messages for suggestions was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25381) in GitLab 13.9 behind a [feature flag](../../../feature_flags.md), disabled by default.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25381) custom commit messages for suggestions in GitLab 13.9 [with a flag](../../../../administration/feature_flags.md) named `suggestions_custom_commit`. Disabled by default.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/297404) in GitLab 13.10.
|
||||
|
||||
As a reviewer, you're able to suggest code changes with a Markdown syntax in merge request
|
||||
diff threads. Then, the merge request author (or other users with appropriate
|
||||
[permission](../../../permissions.md)) is able to apply these suggestions with a click,
|
||||
which generates a commit in the merge request authored by the user that suggested the changes.
|
||||
[permission](../../../permissions.md)) can apply these suggestions with a click.
|
||||
This action generates a commit in the merge request, authored by the user that suggested the changes.
|
||||
|
||||
1. Choose a line of code to be changed, add a new comment, then select
|
||||
the **Insert suggestion** icon in the toolbar:
|
||||
|
@ -38,15 +37,16 @@ which generates a commit in the merge request authored by the user that suggeste
|
|||
|
||||
![Custom commit](img/custom_commit_v13_9.png)
|
||||
|
||||
After the author applies a suggestion, it's marked with the **Applied** label,
|
||||
the thread is automatically resolved, and GitLab creates a new commit
|
||||
and pushes the suggested change directly into the codebase in the merge request's
|
||||
branch. The [Developer role](../../../permissions.md) is required to do so.
|
||||
After the author applies a suggestion:
|
||||
|
||||
1. The suggestion is marked as **Applied**.
|
||||
1. The thread is resolved.
|
||||
1. GitLab creates a new commit with the changes.
|
||||
1. If the user has the [Developer role](../../../permissions.md), GitLab pushes
|
||||
the suggested change directly into the codebase in the merge request's branch.
|
||||
|
||||
## Multi-line suggestions
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/53310) in GitLab 11.10.
|
||||
|
||||
Reviewers can also suggest changes to multiple lines with a single suggestion
|
||||
within merge request diff threads by adjusting the range offsets. The
|
||||
offsets are relative to the position of the diff thread, and specify the
|
||||
|
@ -67,9 +67,9 @@ suggestion.
|
|||
|
||||
## Code block nested in suggestions
|
||||
|
||||
If you need to make a suggestion that involves a
|
||||
[fenced code block](../../../markdown.md#code-spans-and-blocks), wrap your suggestion in four backticks
|
||||
instead of the usual three.
|
||||
To add a suggestion that includes a
|
||||
[fenced code block](../../../markdown.md#code-spans-and-blocks), wrap your suggestion
|
||||
in four backticks instead of three:
|
||||
|
||||
![A comment editor with a suggestion with a fenced code block](img/suggestion_code_block_editor_v12_8.png)
|
||||
|
||||
|
@ -95,14 +95,14 @@ You can also use following variables besides static text:
|
|||
|
||||
| Variable | Description | Output example |
|
||||
|------------------------|-------------|----------------|
|
||||
| `%{branch_name}` | The name of the branch the suggestion(s) was(were) applied to. | `my-feature-branch` |
|
||||
| `%{files_count}` | The number of file(s) to which suggestion(s) was(were) applied.| **2** |
|
||||
| `%{file_paths}` | The path(s) of the file(s) suggestion(s) was(were) applied to. Paths are separated by commas.| `docs/index.md, docs/about.md` |
|
||||
| `%{branch_name}` | The name of the branch to which suggestions were applied. | `my-feature-branch` |
|
||||
| `%{files_count}` | The number of files to which suggestions were applied.| **2** |
|
||||
| `%{file_paths}` | The paths of the file to which suggestions were applied. Paths are separated by commas.| `docs/index.md, docs/about.md` |
|
||||
| `%{project_path}` | The project path. | `my-group/my-project` |
|
||||
| `%{project_name}` | The human-readable name of the project. | **My Project** |
|
||||
| `%{suggestions_count}` | The number of suggestions applied.| **3** |
|
||||
| `%{username}` | The username of the user applying suggestion(s). | `user_1` |
|
||||
| `%{user_full_name}` | The full name of the user applying suggestion(s). | **User 1** |
|
||||
| `%{username}` | The username of the user applying suggestions. | `user_1` |
|
||||
| `%{user_full_name}` | The full name of the user applying suggestions. | **User 1** |
|
||||
|
||||
For example, to customize the commit message to output
|
||||
**Addresses user_1's review**, set the custom text to
|
||||
|
@ -114,32 +114,32 @@ introduced by [#25381](https://gitlab.com/gitlab-org/gitlab/-/issues/25381).
|
|||
|
||||
## Batch suggestions
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25486) in GitLab 13.1 as an [alpha feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha) behind a feature flag, disabled by default.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25486) in GitLab 13.1 as an [alpha feature](https://about.gitlab.com/handbook/product/gitlab-the-product/#alpha) with a flag named `batch_suggestions`, disabled by default.
|
||||
> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/227799) in GitLab 13.2.
|
||||
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320755) in GitLab 13.11.
|
||||
> - Custom commit messages for batch suggestions [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326168) in GitLab 14.4.
|
||||
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/320755) in GitLab 13.11. [Feature flag `batch_suggestions`](https://gitlab.com/gitlab-org/gitlab/-/issues/320755) removed.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326168) custom commit messages for batch suggestions in GitLab 14.4.
|
||||
|
||||
You can apply multiple suggestions at once to reduce the number of commits added
|
||||
to your branch to address your reviewers' requests.
|
||||
|
||||
1. To start a batch of suggestions to apply with a single commit, select **Add suggestion to batch**:
|
||||
|
||||
![A code change suggestion displayed, with the button to add the suggestion to a batch highlighted.](img/add_first_suggestion_to_batch_v13_1.jpg "Add a suggestion to a batch")
|
||||
![A code change suggestion displayed, with the add-suggestion option highlighted.](img/add_first_suggestion_to_batch_v13_1.jpg "Add a suggestion to a batch")
|
||||
|
||||
1. Add as many additional suggestions to the batch as you wish:
|
||||
|
||||
![A code change suggestion displayed, with the button to add an additional suggestion to a batch highlighted.](img/add_another_suggestion_to_batch_v13_1.jpg "Add another suggestion to a batch")
|
||||
![A code change suggestion displayed, with the add-more suggestion option highlighted.](img/add_another_suggestion_to_batch_v13_1.jpg "Add another suggestion to a batch")
|
||||
|
||||
1. To remove suggestions, select **Remove from batch**:
|
||||
|
||||
![A code change suggestion displayed, with the button to remove that suggestion from its batch highlighted.](img/remove_suggestion_from_batch_v13_1.jpg "Remove a suggestion from a batch")
|
||||
![A code change suggestion displayed, with the option to remove that suggestion from its batch highlighted.](img/remove_suggestion_from_batch_v13_1.jpg "Remove a suggestion from a batch")
|
||||
|
||||
1. Having added all the suggestions to your liking, when ready, select **Apply suggestions**. You
|
||||
can optionally specify a custom commit message for [batch suggestions](#batch-suggestions)
|
||||
(GitLab 14.4 and later) to describe your change. If you don't specify it, the default commit
|
||||
message is used.
|
||||
|
||||
![A code change suggestion displayed, with the button to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
|
||||
![A code change suggestion displayed, with the option to apply the batch of suggestions highlighted.](img/apply_batch_of_suggestions_v13_1.jpg "Apply a batch of suggestions")
|
||||
|
||||
WARNING:
|
||||
Suggestions applied from multiple authors creates a commit authored by the user applying the suggestions.
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
|
@ -28,12 +28,29 @@ functionality of a project.
|
|||
|
||||
Adjust your project's name, description, avatar, [default branch](../repository/branches/default.md), and topics:
|
||||
|
||||
![general project settings](img/general_settings_v13_11.png)
|
||||
|
||||
The project description also partially supports [standard Markdown](../../markdown.md#features-extended-from-standard-markdown).
|
||||
You can use [emphasis](../../markdown.md#emphasis), [links](../../markdown.md#links), and
|
||||
[line-breaks](../../markdown.md#line-breaks) to add more context to the project description.
|
||||
|
||||
#### Topics
|
||||
|
||||
Use topics to categorize projects and find similar new projects.
|
||||
|
||||
To assign topics to a project:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Settings** > **General**.
|
||||
1. Under **Topics**, enter the project topics. Existing popular topics are suggested as you type.
|
||||
1. Select **Save changes**.
|
||||
|
||||
For GitLab.com, explore popular topics on the [Explore topics page](https://gitlab.com/explore/projects/topics).
|
||||
When you select a topic, you can see relevant projects.
|
||||
|
||||
NOTE:
|
||||
The assigned topics are visible only to everyone with access to the project,
|
||||
but everyone can see which topics exist at all on the GitLab instance.
|
||||
Do not include sensitive information in the name of a topic.
|
||||
|
||||
#### Compliance frameworks **(PREMIUM)**
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/276221) in GitLab 13.9.
|
||||
|
|
|
@ -530,6 +530,14 @@ module API
|
|||
desc: 'The Mattermost token'
|
||||
}
|
||||
],
|
||||
'shimo' => [
|
||||
{
|
||||
required: true,
|
||||
name: :external_wiki_url,
|
||||
type: String,
|
||||
desc: 'Shimo workspace URL'
|
||||
}
|
||||
],
|
||||
'slack-slash-commands' => [
|
||||
{
|
||||
required: true,
|
||||
|
|
|
@ -141,6 +141,10 @@ module Gitlab
|
|||
scanner <=> other.scanner
|
||||
end
|
||||
|
||||
def has_signatures?
|
||||
signatures.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_project_fingerprint
|
||||
|
|
|
@ -69,6 +69,10 @@ module Gitlab
|
|||
|
||||
primary_scanner <=> other.primary_scanner
|
||||
end
|
||||
|
||||
def has_signatures?
|
||||
findings.any?(&:has_signatures?)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,11 @@ module Gitlab
|
|||
|
||||
class << self
|
||||
def register_models(models)
|
||||
registered_models.merge(models)
|
||||
models.each do |model|
|
||||
raise "#{model} should have partitioning strategy defined" unless model.respond_to?(:partitioning_strategy)
|
||||
|
||||
registered_models << model
|
||||
end
|
||||
end
|
||||
|
||||
def register_tables(tables)
|
||||
|
@ -55,8 +59,6 @@ module Gitlab
|
|||
Gitlab::AppLogger.info(message: 'Finished dropping detached postgres partitions')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def registered_models
|
||||
@registered_models ||= Set.new
|
||||
end
|
||||
|
@ -65,6 +67,8 @@ module Gitlab
|
|||
@registered_tables ||= Set.new
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def registered_for_sync
|
||||
registered_models + registered_tables.map do |table|
|
||||
TableWithoutModel.new(**table)
|
||||
|
|
|
@ -16,6 +16,9 @@ module Gitlab
|
|||
def execute
|
||||
return if merge_request_id.blank?
|
||||
|
||||
note.project = project
|
||||
note.merge_request = merge_request
|
||||
|
||||
build_author_attributes
|
||||
|
||||
# Diff notes with suggestions are imported with DiffNote, which is
|
||||
|
@ -68,10 +71,10 @@ module Gitlab
|
|||
# allows us to efficiently insert data (even if it's just 1 row)
|
||||
# without having to use all sorts of hacks to disable callbacks.
|
||||
Gitlab::Database.main.bulk_insert(LegacyDiffNote.table_name, [{
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_type: note.noteable_type,
|
||||
system: false,
|
||||
type: 'LegacyDiffNote',
|
||||
discussion_id: Discussion.discussion_id(note),
|
||||
discussion_id: note.discussion_id,
|
||||
noteable_id: merge_request_id,
|
||||
project_id: project.id,
|
||||
author_id: author_id,
|
||||
|
@ -89,16 +92,17 @@ module Gitlab
|
|||
log_diff_note_creation('DiffNote')
|
||||
|
||||
::Import::Github::Notes::CreateService.new(project, author, {
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_type: note.noteable_type,
|
||||
system: false,
|
||||
type: 'DiffNote',
|
||||
noteable_id: merge_request_id,
|
||||
project_id: project.id,
|
||||
note: note_body,
|
||||
discussion_id: note.discussion_id,
|
||||
commit_id: note.original_commit_id,
|
||||
created_at: note.created_at,
|
||||
updated_at: note.updated_at,
|
||||
position: note.diff_position(merge_request)
|
||||
position: note.diff_position
|
||||
}).execute
|
||||
end
|
||||
|
||||
|
|
|
@ -7,14 +7,14 @@ module Gitlab
|
|||
include ToHash
|
||||
include ExposeAttribute
|
||||
|
||||
attr_reader :attributes
|
||||
|
||||
expose_attribute :noteable_type, :noteable_id, :commit_id, :file_path,
|
||||
:diff_hunk, :author, :created_at, :updated_at,
|
||||
:original_commit_id, :note_id, :end_line, :start_line,
|
||||
:side
|
||||
|
||||
NOTEABLE_TYPE = 'MergeRequest'
|
||||
NOTEABLE_ID_REGEX = %r{/pull/(?<iid>\d+)}i.freeze
|
||||
DISCUSSION_CACHE_KEY = 'github-importer/discussion-id-map/%{project_id}/%{noteable_id}/%{original_note_id}'
|
||||
|
||||
expose_attribute :noteable_id, :commit_id, :file_path,
|
||||
:diff_hunk, :author, :created_at, :updated_at,
|
||||
:original_commit_id, :note_id, :end_line, :start_line,
|
||||
:side, :in_reply_to_id
|
||||
|
||||
# Builds a diff note from a GitHub API response.
|
||||
#
|
||||
|
@ -31,7 +31,6 @@ module Gitlab
|
|||
|
||||
user = Representation::User.from_api_response(note.user) if note.user
|
||||
hash = {
|
||||
noteable_type: 'MergeRequest',
|
||||
noteable_id: matches[:iid].to_i,
|
||||
file_path: note.path,
|
||||
commit_id: note.commit_id,
|
||||
|
@ -44,7 +43,8 @@ module Gitlab
|
|||
note_id: note.id,
|
||||
end_line: note.line,
|
||||
start_line: note.start_line,
|
||||
side: note.side
|
||||
side: note.side,
|
||||
in_reply_to_id: note.in_reply_to_id
|
||||
}
|
||||
|
||||
new(hash)
|
||||
|
@ -58,6 +58,8 @@ module Gitlab
|
|||
new(hash)
|
||||
end
|
||||
|
||||
attr_accessor :merge_request, :project
|
||||
|
||||
# attributes - A Hash containing the raw note details. The keys of this
|
||||
# Hash must be Symbols.
|
||||
def initialize(attributes)
|
||||
|
@ -70,6 +72,10 @@ module Gitlab
|
|||
)
|
||||
end
|
||||
|
||||
def noteable_type
|
||||
NOTEABLE_TYPE
|
||||
end
|
||||
|
||||
def contains_suggestion?
|
||||
@note_formatter.contains_suggestion?
|
||||
end
|
||||
|
@ -102,7 +108,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
# Used when importing with DiffNote
|
||||
def diff_position(merge_request)
|
||||
def diff_position
|
||||
position_params = {
|
||||
diff_refs: merge_request.diff_refs,
|
||||
old_path: file_path,
|
||||
|
@ -120,8 +126,25 @@ module Gitlab
|
|||
}
|
||||
end
|
||||
|
||||
def discussion_id
|
||||
if in_reply_to_id.present?
|
||||
current_discussion_id
|
||||
else
|
||||
Discussion.discussion_id(
|
||||
Struct
|
||||
.new(:noteable_id, :noteable_type)
|
||||
.new(merge_request.id, NOTEABLE_TYPE)
|
||||
).tap do |discussion_id|
|
||||
cache_discussion_id(discussion_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Required by ExposeAttribute
|
||||
attr_reader :attributes
|
||||
|
||||
def diff_line_params
|
||||
if addition?
|
||||
{ new_line: end_line, old_line: nil }
|
||||
|
@ -133,6 +156,22 @@ module Gitlab
|
|||
def addition?
|
||||
side == 'RIGHT'
|
||||
end
|
||||
|
||||
def cache_discussion_id(discussion_id)
|
||||
Gitlab::Cache::Import::Caching.write(discussion_id_cache_key(note_id), discussion_id)
|
||||
end
|
||||
|
||||
def current_discussion_id
|
||||
Gitlab::Cache::Import::Caching.read(discussion_id_cache_key(in_reply_to_id))
|
||||
end
|
||||
|
||||
def discussion_id_cache_key(id)
|
||||
DISCUSSION_CACHE_KEY % {
|
||||
project_id: project.id,
|
||||
noteable_id: merge_request.id,
|
||||
original_note_id: id
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,11 @@ module Gitlab
|
|||
def to_caller_id
|
||||
"graphql:#{name}"
|
||||
end
|
||||
|
||||
def query_urgency
|
||||
# We'll be able to actually correlate query_urgency with https://gitlab.com/gitlab-org/gitlab/-/issues/345141
|
||||
::Gitlab::EndpointAttributes::DEFAULT_URGENCY
|
||||
end
|
||||
end
|
||||
|
||||
ANONYMOUS = Operation.new("anonymous").freeze
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Graphql
|
||||
module Tracers
|
||||
class MetricsTracer
|
||||
def self.use(schema)
|
||||
schema.tracer(self.new)
|
||||
end
|
||||
|
||||
# See https://graphql-ruby.org/api-doc/1.12.16/GraphQL/Tracing for full list of events
|
||||
def trace(key, data)
|
||||
result = yield
|
||||
|
||||
case key
|
||||
when "execute_query"
|
||||
increment_query_sli(data)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def increment_query_sli(data)
|
||||
duration_s = data.fetch(:duration_s, nil)
|
||||
query = data.fetch(:query, nil)
|
||||
|
||||
# We're just being defensive here...
|
||||
# duration_s comes from TimerTracer and we should be pretty much guaranteed it exists
|
||||
return unless duration_s && query
|
||||
|
||||
operation = ::Gitlab::Graphql::KnownOperations.default.from_query(query)
|
||||
query_urgency = operation.query_urgency
|
||||
|
||||
Gitlab::Metrics::RailsSlis.graphql_query_apdex.increment(
|
||||
labels: {
|
||||
endpoint_id: ::Gitlab::ApplicationContext.current_context_attribute(:caller_id),
|
||||
feature_category: ::Gitlab::ApplicationContext.current_context_attribute(:feature_category),
|
||||
query_urgency: query_urgency.name
|
||||
},
|
||||
success: duration_s <= query_urgency.duration
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,7 +7,7 @@ module Gitlab
|
|||
Asana Assembla Bamboo Bugzilla Buildkite Campfire Confluence CustomIssueTracker Datadog
|
||||
Discord DroneCi EmailsOnPush Ewm ExternalWiki Flowdock HangoutsChat Irker Jenkins Jira Mattermost
|
||||
MattermostSlashCommands MicrosoftTeams MockCi MockMonitoring Packagist PipelinesEmail Pivotaltracker
|
||||
Prometheus Pushover Redmine Slack SlackSlashCommands Teamcity UnifyCircuit WebexTeams Youtrack Zentao
|
||||
Prometheus Pushover Redmine Shimo Slack SlackSlashCommands Teamcity UnifyCircuit WebexTeams Youtrack Zentao
|
||||
)).freeze
|
||||
|
||||
def self.namespaced_integrations
|
||||
|
|
|
@ -5,17 +5,31 @@ module Gitlab
|
|||
module RailsSlis
|
||||
class << self
|
||||
def initialize_request_slis_if_needed!
|
||||
return if Gitlab::Metrics::Sli.initialized?(:rails_request_apdex)
|
||||
|
||||
Gitlab::Metrics::Sli.initialize_sli(:rails_request_apdex, possible_request_labels)
|
||||
Gitlab::Metrics::Sli.initialize_sli(:rails_request_apdex, possible_request_labels) unless Gitlab::Metrics::Sli.initialized?(:rails_request_apdex)
|
||||
Gitlab::Metrics::Sli.initialize_sli(:graphql_query_apdex, possible_graphql_query_labels) unless Gitlab::Metrics::Sli.initialized?(:graphql_query_apdex)
|
||||
end
|
||||
|
||||
def request_apdex
|
||||
Gitlab::Metrics::Sli[:rails_request_apdex]
|
||||
end
|
||||
|
||||
def graphql_query_apdex
|
||||
Gitlab::Metrics::Sli[:graphql_query_apdex]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def possible_graphql_query_labels
|
||||
::Gitlab::Graphql::KnownOperations.default.operations.map do |op|
|
||||
{
|
||||
endpoint_id: op.to_caller_id,
|
||||
# We'll be able to correlate feature_category with https://gitlab.com/gitlab-org/gitlab/-/issues/328535
|
||||
feature_category: nil,
|
||||
query_urgency: op.query_urgency.name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def possible_request_labels
|
||||
possible_controller_labels + possible_api_labels
|
||||
end
|
||||
|
|
|
@ -1139,6 +1139,9 @@ msgstr ""
|
|||
msgid "* All times are in UTC unless specified"
|
||||
msgstr ""
|
||||
|
||||
msgid "*Required"
|
||||
msgstr ""
|
||||
|
||||
msgid "+ %{amount} more"
|
||||
msgstr ""
|
||||
|
||||
|
@ -27216,6 +27219,9 @@ msgstr ""
|
|||
msgid "ProjectSettings|Requirements management system."
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Search for topic"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProjectSettings|Security & Compliance"
|
||||
msgstr ""
|
||||
|
||||
|
@ -29190,7 +29196,7 @@ msgstr ""
|
|||
msgid "Repository update events"
|
||||
msgstr ""
|
||||
|
||||
msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}"
|
||||
msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / Pipeline Artifacts: %{counter_pipeline_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets} / Packages: %{counter_packages} / Uploads: %{counter_uploads}"
|
||||
msgstr ""
|
||||
|
||||
msgid "RepositorySettingsAccessLevel|Select"
|
||||
|
@ -31299,9 +31305,6 @@ msgstr ""
|
|||
msgid "Sep"
|
||||
msgstr ""
|
||||
|
||||
msgid "Separate topics with commas."
|
||||
msgstr ""
|
||||
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
|
@ -31751,6 +31754,15 @@ msgstr ""
|
|||
msgid "Sherlock Transactions"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shimo|Link to a Shimo Workspace from the sidebar."
|
||||
msgstr ""
|
||||
|
||||
msgid "Shimo|Shimo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Shimo|Shimo Workspace URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account."
|
||||
msgstr ""
|
||||
|
||||
|
@ -36217,9 +36229,6 @@ msgstr ""
|
|||
msgid "Topics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Topics (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/svgs": "1.220.0",
|
||||
"@gitlab/tributejs": "1.0.0",
|
||||
"@gitlab/ui": "32.33.0",
|
||||
"@gitlab/ui": "32.36.0",
|
||||
"@gitlab/visual-review-tools": "1.6.1",
|
||||
"@rails/actioncable": "6.1.4-1",
|
||||
"@rails/ujs": "6.1.4-1",
|
||||
|
|
|
@ -111,6 +111,12 @@ FactoryBot.define do
|
|||
end
|
||||
end
|
||||
|
||||
factory :shimo_integration, class: 'Integrations::Shimo' do
|
||||
project
|
||||
active { true }
|
||||
external_wiki_url { 'https://shimo.example.com/desktop' }
|
||||
end
|
||||
|
||||
factory :confluence_integration, class: 'Integrations::Confluence' do
|
||||
project
|
||||
active { true }
|
||||
|
|
|
@ -16,7 +16,7 @@ RSpec.describe "Admin > Admin sees project statistics" do
|
|||
let(:project) { create(:project, :repository) }
|
||||
|
||||
it "shows project statistics" do
|
||||
expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes / Packages: 0 Bytes / Uploads: 0 Bytes)")
|
||||
expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / Pipeline Artifacts: 0 Bytes / LFS: 0 Bytes / Snippets: 0 Bytes / Packages: 0 Bytes / Uploads: 0 Bytes)")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,22 +2,40 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Projects > Settings > User tags a project' do
|
||||
RSpec.describe 'Projects > Settings > User tags a project', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, namespace: user.namespace) }
|
||||
let!(:topic) { create(:topic, name: 'topic1') }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
visit edit_project_path(project)
|
||||
wait_for_all_requests
|
||||
end
|
||||
|
||||
it 'sets project topics' do
|
||||
fill_in 'Topics', with: 'topic1, topic2'
|
||||
it 'select existing topic' do
|
||||
fill_in class: 'gl-token-selector-input', with: 'topic1'
|
||||
wait_for_all_requests
|
||||
|
||||
find('.gl-avatar-labeled[entity-name="topic1"]').click
|
||||
|
||||
page.within '.general-settings' do
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
||||
expect(find_field('Topics').value).to eq 'topic1, topic2'
|
||||
expect(find('#project_topic_list_field', visible: :hidden).value).to eq 'topic1'
|
||||
end
|
||||
|
||||
it 'select new topic' do
|
||||
fill_in class: 'gl-token-selector-input', with: 'topic2'
|
||||
wait_for_all_requests
|
||||
|
||||
click_button 'Add "topic2"'
|
||||
|
||||
page.within '.general-settings' do
|
||||
click_button 'Save changes'
|
||||
end
|
||||
|
||||
expect(find('#project_topic_list_field', visible: :hidden).value).to eq 'topic2'
|
||||
end
|
||||
end
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,98 @@
|
|||
import { GlTokenSelector, GlToken } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import TopicsTokenSelector from '~/projects/settings/topics/components/topics_token_selector.vue';
|
||||
|
||||
const mockTopics = [
|
||||
{ id: 1, name: 'topic1', avatarUrl: 'avatar.com/topic1.png' },
|
||||
{ id: 2, name: 'GitLab', avatarUrl: 'avatar.com/GitLab.png' },
|
||||
];
|
||||
|
||||
describe('TopicsTokenSelector', () => {
|
||||
let wrapper;
|
||||
let div;
|
||||
let input;
|
||||
|
||||
const createComponent = (selected) => {
|
||||
wrapper = mount(TopicsTokenSelector, {
|
||||
attachTo: div,
|
||||
propsData: {
|
||||
selected,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
topics: mockTopics,
|
||||
};
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
queries: {
|
||||
topics: { loading: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findTokenSelector = () => wrapper.findComponent(GlTokenSelector);
|
||||
|
||||
const findTokenSelectorInput = () => findTokenSelector().find('input[type="text"]');
|
||||
|
||||
const setTokenSelectorInputValue = (value) => {
|
||||
const tokenSelectorInput = findTokenSelectorInput();
|
||||
|
||||
tokenSelectorInput.element.value = value;
|
||||
tokenSelectorInput.trigger('input');
|
||||
|
||||
return nextTick();
|
||||
};
|
||||
|
||||
const tokenSelectorTriggerEnter = (event) => {
|
||||
const tokenSelectorInput = findTokenSelectorInput();
|
||||
tokenSelectorInput.trigger('keydown.enter', event);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
div = document.createElement('div');
|
||||
input = document.createElement('input');
|
||||
input.setAttribute('type', 'text');
|
||||
input.id = 'project_topic_list_field';
|
||||
document.body.appendChild(div);
|
||||
document.body.appendChild(input);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
div.remove();
|
||||
input.remove();
|
||||
});
|
||||
|
||||
describe('when component is mounted', () => {
|
||||
it('parses selected into tokens', async () => {
|
||||
const selected = [
|
||||
{ id: 11, name: 'topic1' },
|
||||
{ id: 12, name: 'topic2' },
|
||||
{ id: 13, name: 'topic3' },
|
||||
];
|
||||
createComponent(selected);
|
||||
await nextTick();
|
||||
|
||||
wrapper.findAllComponents(GlToken).wrappers.forEach((tokenWrapper, index) => {
|
||||
expect(tokenWrapper.text()).toBe(selected[index].name);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when enter key is pressed', () => {
|
||||
it('does not submit the form if token selector text input has a value', async () => {
|
||||
createComponent();
|
||||
|
||||
await setTokenSelectorInputValue('topic');
|
||||
|
||||
const event = { preventDefault: jest.fn() };
|
||||
tokenSelectorTriggerEnter(event);
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
import { GlDropdownItem } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import { __ } from '~/locale';
|
||||
import TagFieldNew from '~/releases/components/tag_field_new.vue';
|
||||
import createStore from '~/releases/stores';
|
||||
import createEditNewModule from '~/releases/stores/modules/edit_new';
|
||||
|
@ -84,7 +85,8 @@ describe('releases/components/tag_field_new', () => {
|
|||
beforeEach(() => createComponent());
|
||||
|
||||
it('renders a label', () => {
|
||||
expect(findTagNameFormGroup().attributes().label).toBe('Tag name');
|
||||
expect(findTagNameFormGroup().attributes().label).toBe(__('Tag name'));
|
||||
expect(findTagNameFormGroup().props().labelDescription).toBe(__('*Required'));
|
||||
});
|
||||
|
||||
describe('when the user selects a new tag name', () => {
|
||||
|
|
|
@ -1,36 +1,68 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import collapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
|
||||
describe('collapsedCalendarIcon', () => {
|
||||
let vm;
|
||||
beforeEach(() => {
|
||||
const CollapsedCalendarIcon = Vue.extend(collapsedCalendarIcon);
|
||||
vm = mountComponent(CollapsedCalendarIcon, {
|
||||
containerClass: 'test-class',
|
||||
text: 'text',
|
||||
showIcon: false,
|
||||
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
|
||||
|
||||
describe('CollapsedCalendarIcon', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
containerClass: 'test-class',
|
||||
text: 'text',
|
||||
tooltipText: 'tooltip text',
|
||||
showIcon: false,
|
||||
};
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMount(CollapsedCalendarIcon, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should add class to container', () => {
|
||||
expect(vm.$el.classList.contains('test-class')).toEqual(true);
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
it('should hide calendar icon if showIcon', () => {
|
||||
expect(vm.$el.querySelector('[data-testid="calendar-icon"]')).toBeNull();
|
||||
const findGlIcon = () => wrapper.findComponent(GlIcon);
|
||||
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
|
||||
|
||||
it('adds class to container', () => {
|
||||
expect(wrapper.classes()).toContain(defaultProps.containerClass);
|
||||
});
|
||||
|
||||
it('should render text', () => {
|
||||
expect(vm.$el.querySelector('span').innerText.trim()).toEqual('text');
|
||||
it('does not render calendar icon when showIcon is false', () => {
|
||||
expect(findGlIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should emit click event when container is clicked', () => {
|
||||
const click = jest.fn();
|
||||
vm.$on('click', click);
|
||||
it('renders calendar icon when showIcon is true', () => {
|
||||
createComponent({
|
||||
props: { showIcon: true },
|
||||
});
|
||||
|
||||
vm.$el.click();
|
||||
expect(findGlIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
expect(click).toHaveBeenCalled();
|
||||
it('renders text', () => {
|
||||
expect(wrapper.text()).toBe(defaultProps.text);
|
||||
});
|
||||
|
||||
it('renders tooltipText as tooltip', () => {
|
||||
expect(getTooltip().value).toBe(defaultProps.tooltipText);
|
||||
});
|
||||
|
||||
it('emits click event when container is clicked', async () => {
|
||||
wrapper.trigger('click');
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted('click')[0]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,86 +1,103 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import collapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
describe('collapsedGroupedDatePicker', () => {
|
||||
let vm;
|
||||
beforeEach(() => {
|
||||
const CollapsedGroupedDatePicker = Vue.extend(collapsedGroupedDatePicker);
|
||||
vm = mountComponent(CollapsedGroupedDatePicker, {
|
||||
showToggleSidebar: true,
|
||||
import CollapsedGroupedDatePicker from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
|
||||
import CollapsedCalendarIcon from '~/vue_shared/components/sidebar/collapsed_calendar_icon.vue';
|
||||
|
||||
describe('CollapsedGroupedDatePicker', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
showToggleSidebar: true,
|
||||
};
|
||||
|
||||
const minDate = new Date('07/17/2016');
|
||||
const maxDate = new Date('07/17/2017');
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMount(CollapsedGroupedDatePicker, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findCollapsedCalendarIcon = () => wrapper.findComponent(CollapsedCalendarIcon);
|
||||
const findAllCollapsedCalendarIcons = () => wrapper.findAllComponents(CollapsedCalendarIcon);
|
||||
|
||||
describe('toggleCollapse events', () => {
|
||||
beforeEach((done) => {
|
||||
jest.spyOn(vm, 'toggleSidebar').mockImplementation(() => {});
|
||||
vm.minDate = new Date('07/17/2016');
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('should emit when collapsed-calendar-icon is clicked', () => {
|
||||
vm.$el.querySelector('.sidebar-collapsed-icon').click();
|
||||
createComponent();
|
||||
|
||||
expect(vm.toggleSidebar).toHaveBeenCalled();
|
||||
findCollapsedCalendarIcon().trigger('click');
|
||||
|
||||
expect(wrapper.emitted('toggleCollapse')[0]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('minDate and maxDate', () => {
|
||||
beforeEach((done) => {
|
||||
vm.minDate = new Date('07/17/2016');
|
||||
vm.maxDate = new Date('07/17/2017');
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('should render both collapsed-calendar-icon', () => {
|
||||
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
|
||||
createComponent({
|
||||
props: {
|
||||
minDate,
|
||||
maxDate,
|
||||
},
|
||||
});
|
||||
|
||||
expect(icons.length).toEqual(2);
|
||||
expect(icons[0].innerText.trim()).toEqual('Jul 17 2016');
|
||||
expect(icons[1].innerText.trim()).toEqual('Jul 17 2017');
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toBe(2);
|
||||
expect(icons.at(0).text()).toBe('Jul 17 2016');
|
||||
expect(icons.at(1).text()).toBe('Jul 17 2017');
|
||||
});
|
||||
});
|
||||
|
||||
describe('minDate', () => {
|
||||
beforeEach((done) => {
|
||||
vm.minDate = new Date('07/17/2016');
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('should render minDate in collapsed-calendar-icon', () => {
|
||||
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
|
||||
createComponent({
|
||||
props: {
|
||||
minDate,
|
||||
},
|
||||
});
|
||||
|
||||
expect(icons.length).toEqual(1);
|
||||
expect(icons[0].innerText.trim()).toEqual('From Jul 17 2016');
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toBe(1);
|
||||
expect(icons.at(0).text()).toBe('From Jul 17 2016');
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxDate', () => {
|
||||
beforeEach((done) => {
|
||||
vm.maxDate = new Date('07/17/2017');
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('should render maxDate in collapsed-calendar-icon', () => {
|
||||
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
|
||||
createComponent({
|
||||
props: {
|
||||
maxDate,
|
||||
},
|
||||
});
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toEqual(1);
|
||||
expect(icons[0].innerText.trim()).toEqual('Until Jul 17 2017');
|
||||
expect(icons.length).toBe(1);
|
||||
expect(icons.at(0).text()).toBe('Until Jul 17 2017');
|
||||
});
|
||||
});
|
||||
|
||||
describe('no dates', () => {
|
||||
it('should render None', () => {
|
||||
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
expect(icons.length).toEqual(1);
|
||||
expect(icons[0].innerText.trim()).toEqual('None');
|
||||
it('should render None', () => {
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons.length).toBe(1);
|
||||
expect(icons.at(0).text()).toBe('None');
|
||||
});
|
||||
|
||||
it('should have tooltip as `Start and due date`', () => {
|
||||
const icons = vm.$el.querySelectorAll('.sidebar-collapsed-icon');
|
||||
const icons = findAllCollapsedCalendarIcons();
|
||||
|
||||
expect(icons[0].title).toBe('Start and due date');
|
||||
expect(icons.at(0).props('tooltipText')).toBe('Start and due date');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import DatePicker from '~/vue_shared/components/pikaday.vue';
|
||||
import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
|
||||
|
@ -5,14 +6,8 @@ import SidebarDatePicker from '~/vue_shared/components/sidebar/date_picker.vue';
|
|||
describe('SidebarDatePicker', () => {
|
||||
let wrapper;
|
||||
|
||||
const mountComponent = (propsData = {}, data = {}) => {
|
||||
if (wrapper) {
|
||||
throw new Error('tried to call mountComponent without d');
|
||||
}
|
||||
const createComponent = (propsData = {}, data = {}) => {
|
||||
wrapper = mount(SidebarDatePicker, {
|
||||
stubs: {
|
||||
DatePicker: true,
|
||||
},
|
||||
propsData,
|
||||
data: () => data,
|
||||
});
|
||||
|
@ -20,87 +15,93 @@ describe('SidebarDatePicker', () => {
|
|||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
|
||||
mountComponent();
|
||||
const findDatePicker = () => wrapper.findComponent(DatePicker);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findEditButton = () => wrapper.find('.title .btn-blank');
|
||||
const findRemoveButton = () => wrapper.find('.value-content .btn-blank');
|
||||
const findSidebarToggle = () => wrapper.find('.title .gutter-toggle');
|
||||
const findValueContent = () => wrapper.find('.value-content');
|
||||
|
||||
wrapper.find('.issuable-sidebar-header .gutter-toggle').element.click();
|
||||
it('should emit toggleCollapse when collapsed toggle sidebar is clicked', () => {
|
||||
createComponent();
|
||||
|
||||
wrapper.find('.issuable-sidebar-header .gutter-toggle').trigger('click');
|
||||
|
||||
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('should render collapsed-calendar-icon', () => {
|
||||
mountComponent();
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find('.sidebar-collapsed-icon').element).toBeDefined();
|
||||
expect(wrapper.find('.sidebar-collapsed-icon').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render value when not editing', () => {
|
||||
mountComponent();
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find('.value-content').element).toBeDefined();
|
||||
expect(findValueContent().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render None if there is no selectedDate', () => {
|
||||
mountComponent();
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find('.value-content span').text().trim()).toEqual('None');
|
||||
expect(findValueContent().text()).toBe('None');
|
||||
});
|
||||
|
||||
it('should render date-picker when editing', () => {
|
||||
mountComponent({}, { editing: true });
|
||||
createComponent({}, { editing: true });
|
||||
|
||||
expect(wrapper.find(DatePicker).element).toBeDefined();
|
||||
expect(findDatePicker().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should render label', () => {
|
||||
const label = 'label';
|
||||
mountComponent({ label });
|
||||
expect(wrapper.find('.title').text().trim()).toEqual(label);
|
||||
createComponent({ label });
|
||||
expect(wrapper.find('.title').text()).toBe(label);
|
||||
});
|
||||
|
||||
it('should render loading-icon when isLoading', () => {
|
||||
mountComponent({ isLoading: true });
|
||||
expect(wrapper.find('.gl-spinner').element).toBeDefined();
|
||||
createComponent({ isLoading: true });
|
||||
expect(findLoadingIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('editable', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ editable: true });
|
||||
createComponent({ editable: true });
|
||||
});
|
||||
|
||||
it('should render edit button', () => {
|
||||
expect(wrapper.find('.title .btn-blank').text().trim()).toEqual('Edit');
|
||||
expect(findEditButton().text()).toBe('Edit');
|
||||
});
|
||||
|
||||
it('should enable editing when edit button is clicked', async () => {
|
||||
wrapper.find('.title .btn-blank').element.click();
|
||||
findEditButton().trigger('click');
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.vm.editing).toEqual(true);
|
||||
expect(wrapper.vm.editing).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render date if selectedDate', () => {
|
||||
mountComponent({ selectedDate: new Date('07/07/2017') });
|
||||
createComponent({ selectedDate: new Date('07/07/2017') });
|
||||
|
||||
expect(wrapper.find('.value-content strong').text().trim()).toEqual('Jul 7, 2017');
|
||||
expect(wrapper.find('.value-content strong').text()).toBe('Jul 7, 2017');
|
||||
});
|
||||
|
||||
describe('selectedDate and editable', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ selectedDate: new Date('07/07/2017'), editable: true });
|
||||
createComponent({ selectedDate: new Date('07/07/2017'), editable: true });
|
||||
});
|
||||
|
||||
it('should render remove button if selectedDate and editable', () => {
|
||||
expect(wrapper.find('.value-content .btn-blank').text().trim()).toEqual('remove');
|
||||
expect(findRemoveButton().text()).toBe('remove');
|
||||
});
|
||||
|
||||
it('should emit saveDate with null when remove button is clicked', () => {
|
||||
wrapper.find('.value-content .btn-blank').element.click();
|
||||
findRemoveButton().trigger('click');
|
||||
|
||||
expect(wrapper.emitted('saveDate')).toEqual([[null]]);
|
||||
});
|
||||
|
@ -108,15 +109,15 @@ describe('SidebarDatePicker', () => {
|
|||
|
||||
describe('showToggleSidebar', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({ showToggleSidebar: true });
|
||||
createComponent({ showToggleSidebar: true });
|
||||
});
|
||||
|
||||
it('should render toggle-sidebar when showToggleSidebar', () => {
|
||||
expect(wrapper.find('.title .gutter-toggle').element).toBeDefined();
|
||||
expect(findSidebarToggle().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should emit toggleCollapse when toggle sidebar is clicked', () => {
|
||||
wrapper.find('.title .gutter-toggle').element.click();
|
||||
findSidebarToggle().trigger('click');
|
||||
|
||||
expect(wrapper.emitted('toggleCollapse')).toEqual([[]]);
|
||||
});
|
||||
|
|
|
@ -1,95 +1,74 @@
|
|||
import Vue from 'vue';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlIcon } from '@gitlab/ui';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import DropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue';
|
||||
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select_vue/dropdown_value_collapsed.vue';
|
||||
|
||||
import { mockCollapsedLabels as mockLabels } from './mock_data';
|
||||
|
||||
const createComponent = (labels = mockLabels) => {
|
||||
const Component = Vue.extend(dropdownValueCollapsedComponent);
|
||||
|
||||
return mountComponent(Component, {
|
||||
labels,
|
||||
});
|
||||
};
|
||||
import { mockCollapsedLabels as mockLabels, mockRegularLabel } from './mock_data';
|
||||
|
||||
describe('DropdownValueCollapsedComponent', () => {
|
||||
let vm;
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
});
|
||||
const defaultProps = {
|
||||
labels: [],
|
||||
};
|
||||
|
||||
const mockManyLabels = [...mockLabels, ...mockLabels, ...mockLabels];
|
||||
|
||||
const createComponent = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMount(DropdownValueCollapsedComponent, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
directives: {
|
||||
GlTooltip: createMockDirective(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('labelsList', () => {
|
||||
it('returns default text when `labels` prop is empty array', () => {
|
||||
const vmEmptyLabels = createComponent([]);
|
||||
|
||||
expect(vmEmptyLabels.labelsList).toBe('Labels');
|
||||
vmEmptyLabels.$destroy();
|
||||
});
|
||||
|
||||
it('returns labels names separated by coma when `labels` prop has more than one item', () => {
|
||||
const labels = mockLabels.concat(mockLabels);
|
||||
const vmMoreLabels = createComponent(labels);
|
||||
|
||||
const expectedText = labels.map((label) => label.title).join(', ');
|
||||
|
||||
expect(vmMoreLabels.labelsList).toBe(expectedText);
|
||||
vmMoreLabels.$destroy();
|
||||
});
|
||||
|
||||
it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => {
|
||||
const mockMoreLabels = Object.assign([], mockLabels);
|
||||
for (let i = 0; i < 6; i += 1) {
|
||||
mockMoreLabels.unshift(mockLabels[0]);
|
||||
}
|
||||
|
||||
const vmMoreLabels = createComponent(mockMoreLabels);
|
||||
|
||||
const expectedText = `${mockMoreLabels
|
||||
.slice(0, 5)
|
||||
.map((label) => label.title)
|
||||
.join(', ')}, and ${mockMoreLabels.length - 5} more`;
|
||||
|
||||
expect(vmMoreLabels.labelsList).toBe(expectedText);
|
||||
vmMoreLabels.$destroy();
|
||||
});
|
||||
|
||||
it('returns first label name when `labels` prop has only one item present', () => {
|
||||
const text = mockLabels.map((label) => label.title).join(', ');
|
||||
|
||||
expect(vm.labelsList).toBe(text);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('handleClick', () => {
|
||||
it('emits onValueClick event on component', () => {
|
||||
jest.spyOn(vm, '$emit').mockImplementation(() => {});
|
||||
vm.handleClick();
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('onValueClick');
|
||||
});
|
||||
});
|
||||
});
|
||||
const findGlIcon = () => wrapper.findComponent(GlIcon);
|
||||
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip');
|
||||
|
||||
describe('template', () => {
|
||||
it('renders component container element with tooltip`', () => {
|
||||
expect(vm.$el.title).toBe(vm.labelsList);
|
||||
});
|
||||
|
||||
it('renders tags icon element', () => {
|
||||
expect(vm.$el.querySelector('[data-testid="labels-icon"]')).not.toBeNull();
|
||||
createComponent();
|
||||
|
||||
expect(findGlIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders labels count', () => {
|
||||
expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`);
|
||||
it('emits onValueClick event on click', async () => {
|
||||
createComponent();
|
||||
|
||||
wrapper.trigger('click');
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted('onValueClick')[0]).toBeDefined();
|
||||
});
|
||||
|
||||
describe.each`
|
||||
scenario | labels | expectedResult | expectedText
|
||||
${'`labels` is empty'} | ${[]} | ${'default text'} | ${'Labels'}
|
||||
${'`labels` has 1 item'} | ${[mockRegularLabel]} | ${'label name'} | ${'Foo Label'}
|
||||
${'`labels` has 2 items'} | ${mockLabels} | ${'comma separated label names'} | ${'Foo Label, Foo::Bar'}
|
||||
${'`labels` has more than 5 items'} | ${mockManyLabels} | ${'comma separated label names with "and more" phrase'} | ${'Foo Label, Foo::Bar, Foo Label, Foo::Bar, Foo Label, and 1 more'}
|
||||
`('when $scenario', ({ labels, expectedResult, expectedText }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
props: {
|
||||
labels,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders labels count', () => {
|
||||
expect(wrapper.text()).toBe(`${labels.length}`);
|
||||
});
|
||||
|
||||
it(`renders "${expectedResult}" as tooltip`, () => {
|
||||
expect(getTooltip().value).toBe(expectedText);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,31 +1,45 @@
|
|||
import Vue from 'vue';
|
||||
import mountComponent from 'helpers/vue_mount_component_helper';
|
||||
import toggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
|
||||
import { GlButton } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
|
||||
describe('toggleSidebar', () => {
|
||||
let vm;
|
||||
beforeEach(() => {
|
||||
const ToggleSidebar = Vue.extend(toggleSidebar);
|
||||
vm = mountComponent(ToggleSidebar, {
|
||||
collapsed: true,
|
||||
import ToggleSidebar from '~/vue_shared/components/sidebar/toggle_sidebar.vue';
|
||||
|
||||
describe('ToggleSidebar', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
collapsed: true,
|
||||
};
|
||||
|
||||
const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => {
|
||||
wrapper = mountFn(ToggleSidebar, {
|
||||
propsData: { ...defaultProps, ...props },
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findGlButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
it('should render the "chevron-double-lg-left" icon when collapsed', () => {
|
||||
expect(vm.$el.querySelector('[data-testid="chevron-double-lg-left-icon"]')).not.toBeNull();
|
||||
createComponent();
|
||||
|
||||
expect(findGlButton().props('icon')).toBe('chevron-double-lg-left');
|
||||
});
|
||||
|
||||
it('should render the "chevron-double-lg-right" icon when expanded', async () => {
|
||||
vm.collapsed = false;
|
||||
await Vue.nextTick();
|
||||
expect(vm.$el.querySelector('[data-testid="chevron-double-lg-right-icon"]')).not.toBeNull();
|
||||
createComponent({ props: { collapsed: false } });
|
||||
|
||||
expect(findGlButton().props('icon')).toBe('chevron-double-lg-right');
|
||||
});
|
||||
|
||||
it('should emit toggle event when button clicked', () => {
|
||||
const toggle = jest.fn();
|
||||
vm.$on('toggle', toggle);
|
||||
vm.$el.click();
|
||||
it('should emit toggle event when button clicked', async () => {
|
||||
createComponent({ mountFn: mount });
|
||||
|
||||
expect(toggle).toHaveBeenCalled();
|
||||
findGlButton().trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted('toggle')[0]).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Resolvers::TopicsResolver do
|
||||
include GraphqlHelpers
|
||||
|
||||
describe '#resolve' do
|
||||
let!(:topic1) { create(:topic, name: 'GitLab', total_projects_count: 1) }
|
||||
let!(:topic2) { create(:topic, name: 'git', total_projects_count: 2) }
|
||||
let!(:topic3) { create(:topic, name: 'topic3', total_projects_count: 3) }
|
||||
|
||||
it 'finds all topics' do
|
||||
expect(resolve_topics).to eq([topic3, topic2, topic1])
|
||||
end
|
||||
|
||||
context 'with search' do
|
||||
it 'searches environment by name' do
|
||||
expect(resolve_topics(search: 'git')).to eq([topic2, topic1])
|
||||
end
|
||||
|
||||
context 'when the search term does not match any topic' do
|
||||
it 'is empty' do
|
||||
expect(resolve_topics(search: 'nonsense')).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_topics(args = {})
|
||||
resolve(described_class, args: args)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Types::Projects::TopicType do
|
||||
specify { expect(described_class.graphql_name).to eq('Topic') }
|
||||
|
||||
specify do
|
||||
expect(described_class).to have_graphql_fields(
|
||||
:id,
|
||||
:name,
|
||||
:description,
|
||||
:description_html,
|
||||
:avatar_url
|
||||
)
|
||||
end
|
||||
end
|
|
@ -28,6 +28,7 @@ RSpec.describe GitlabSchema.types['Query'] do
|
|||
runners
|
||||
timelogs
|
||||
board_list
|
||||
topics
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields).at_least
|
||||
|
|
|
@ -27,17 +27,18 @@ RSpec.describe StorageHelper do
|
|||
create(:project,
|
||||
namespace: namespace,
|
||||
statistics: build(:project_statistics,
|
||||
namespace: namespace,
|
||||
repository_size: 10.kilobytes,
|
||||
wiki_size: 10.bytes,
|
||||
lfs_objects_size: 20.gigabytes,
|
||||
build_artifacts_size: 30.megabytes,
|
||||
snippets_size: 40.megabytes,
|
||||
packages_size: 12.megabytes,
|
||||
uploads_size: 15.megabytes))
|
||||
namespace: namespace,
|
||||
repository_size: 10.kilobytes,
|
||||
wiki_size: 10.bytes,
|
||||
lfs_objects_size: 20.gigabytes,
|
||||
build_artifacts_size: 30.megabytes,
|
||||
pipeline_artifacts_size: 11.megabytes,
|
||||
snippets_size: 40.megabytes,
|
||||
packages_size: 12.megabytes,
|
||||
uploads_size: 15.megabytes))
|
||||
end
|
||||
|
||||
let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB / Snippets: 40 MB / Packages: 12 MB / Uploads: 15 MB' }
|
||||
let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / Pipeline Artifacts: 11 MB / LFS: 20 GB / Snippets: 40 MB / Packages: 12 MB / Uploads: 15 MB' }
|
||||
|
||||
it 'works on ProjectStatistics' do
|
||||
expect(helper.storage_counters_details(project.statistics)).to eq(message)
|
||||
|
|
|
@ -221,4 +221,26 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#has_signatures?' do
|
||||
let(:finding) { create(:ci_reports_security_finding, signatures: signatures) }
|
||||
|
||||
subject { report.has_signatures? }
|
||||
|
||||
before do
|
||||
report.add_finding(finding)
|
||||
end
|
||||
|
||||
context 'when the findings of the report does not have signatures' do
|
||||
let(:signatures) { [] }
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
end
|
||||
|
||||
context 'when the findings of the report have signatures' do
|
||||
let(:signatures) { [instance_double(Gitlab::Ci::Reports::Security::FindingSignature)] }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,28 @@ RSpec.describe Gitlab::Database::Partitioning do
|
|||
|
||||
let(:connection) { ApplicationRecord.connection }
|
||||
|
||||
around do |example|
|
||||
previously_registered_models = described_class.registered_models.dup
|
||||
described_class.instance_variable_set('@registered_models', Set.new)
|
||||
|
||||
previously_registered_tables = described_class.registered_tables.dup
|
||||
described_class.instance_variable_set('@registered_tables', Set.new)
|
||||
|
||||
example.run
|
||||
|
||||
described_class.instance_variable_set('@registered_models', previously_registered_models)
|
||||
described_class.instance_variable_set('@registered_tables', previously_registered_tables)
|
||||
end
|
||||
|
||||
describe '.register_models' do
|
||||
context 'ensure that the registered models have partitioning strategy' do
|
||||
it 'fails when partitioning_strategy is not specified for the model' do
|
||||
model = Class.new(ApplicationRecord)
|
||||
expect { described_class.register_models([model]) }.to raise_error /should have partitioning strategy defined/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.sync_partitions' do
|
||||
let(:table_names) { %w[partitioning_test1 partitioning_test2] }
|
||||
let(:models) do
|
||||
|
@ -40,24 +62,18 @@ RSpec.describe Gitlab::Database::Partitioning do
|
|||
end
|
||||
|
||||
context 'when no partitioned models are given' do
|
||||
let(:partition_manager_class) { described_class::PartitionManager }
|
||||
let(:partition_manager) { double('partition manager') }
|
||||
let(:model) { double('model') }
|
||||
|
||||
it 'manages partitions for each registered model' do
|
||||
registered_for_sync = described_class.__send__(:registered_for_sync)
|
||||
described_class.register_models([models.first])
|
||||
described_class.register_tables([
|
||||
{
|
||||
table_name: table_names.last,
|
||||
partitioned_column: :created_at, strategy: :monthly
|
||||
}
|
||||
])
|
||||
|
||||
allow(described_class).to receive(:registered_for_sync)
|
||||
.and_return(registered_for_sync)
|
||||
|
||||
expect(Gitlab::Database::EachDatabase).to receive(:each_model_connection)
|
||||
.with(registered_for_sync)
|
||||
.and_yield(model)
|
||||
|
||||
expect(partition_manager_class).to receive(:new).with(model).and_return(partition_manager)
|
||||
expect(partition_manager).to receive(:sync_partitions)
|
||||
|
||||
described_class.sync_partitions
|
||||
expect { described_class.sync_partitions }
|
||||
.to change { find_partitions(table_names.first).size }.from(0)
|
||||
.and change { find_partitions(table_names.last).size }.from(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -125,10 +141,4 @@ RSpec.describe Gitlab::Database::Partitioning do
|
|||
table_oid(table_name).present?
|
||||
end
|
||||
end
|
||||
|
||||
context 'ensure that the registered models have partitioning strategy' do
|
||||
it 'fails when partitioning_strategy is not specified for the model' do
|
||||
expect(described_class.__send__(:registered_models)).to all(respond_to(:partitioning_strategy))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,6 +19,7 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNotesImporter do
|
|||
updated_at: Time.zone.now,
|
||||
line: 23,
|
||||
start_line: nil,
|
||||
in_reply_to_id: nil,
|
||||
id: 1,
|
||||
side: 'RIGHT',
|
||||
body: <<~BODY
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue