Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-15 09:07:59 +00:00
parent 1e9d859394
commit e5731d5194
185 changed files with 2857 additions and 518 deletions

View File

@ -375,7 +375,6 @@ RSpec/LeakyConstantDeclaration:
- 'spec/lib/gitlab/quick_actions/dsl_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/client_metrics_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/server_metrics_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware_spec.rb'
- 'spec/lib/gitlab/view/presenter/factory_spec.rb'
- 'spec/lib/marginalia_spec.rb'
- 'spec/lib/omni_auth/strategies/jwt_spec.rb'

View File

@ -0,0 +1,14 @@
<script>
import { GlDatepicker } from '@gitlab/ui';
export default {
name: 'ExpiresAtField',
components: { GlDatepicker },
};
</script>
<template>
<gl-datepicker :target="null" :min-date="new Date()">
<slot></slot>
</gl-datepicker>
</template>

View File

@ -0,0 +1,12 @@
import Vue from 'vue';
import ExpiresAtField from './components/expires_at_field.vue';
const initExpiresAtField = () => {
// eslint-disable-next-line no-new
new Vue({
el: document.querySelector('.js-access-tokens-expires-at'),
components: { ExpiresAtField },
});
};
export default initExpiresAtField;

View File

@ -1,12 +1,25 @@
<script>
import * as Sentry from '@sentry/browser';
import { GlAlert, GlIcon, GlLoadingIcon, GlSprintf, GlTabs, GlTab, GlButton } from '@gitlab/ui';
import {
GlAlert,
GlIcon,
GlLoadingIcon,
GlDropdown,
GlDropdownItem,
GlSprintf,
GlTabs,
GlTab,
GlButton,
} from '@gitlab/ui';
import createFlash from '~/flash';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { s__ } from '~/locale';
import query from '../graphql/queries/details.query.graphql';
import { fetchPolicies } from '~/lib/graphql';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { ALERTS_SEVERITY_LABELS } from '../constants';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
export default {
statuses: {
@ -29,6 +42,8 @@ export default {
GlIcon,
GlLoadingIcon,
GlSprintf,
GlDropdown,
GlDropdownItem,
GlTab,
GlTabs,
GlButton,
@ -85,9 +100,28 @@ export default {
},
},
methods: {
capitalizeFirstCharacter,
dismissError() {
this.isErrorDismissed = true;
},
updateAlertStatus(status) {
this.$apollo
.mutate({
mutation: updateAlertStatus,
variables: {
iid: this.alertId,
status: status.toUpperCase(),
projectPath: this.projectPath,
},
})
.catch(() => {
createFlash(
s__(
'AlertManagement|There was an error while updating the status of the alert. Please try again.',
),
);
});
},
},
};
</script>
@ -97,7 +131,7 @@ export default {
{{ $options.i18n.errorMsg }}
</gl-alert>
<div v-if="loading"><gl-loading-icon size="lg" class="gl-mt-5" /></div>
<div v-if="alert" class="alert-management-details">
<div v-if="alert" class="alert-management-details gl-relative">
<div
class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-px-1 gl-py-6 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid"
>
@ -137,6 +171,28 @@ export default {
>
<h2 data-testid="title">{{ alert.title }}</h2>
</div>
<gl-dropdown
:text="capitalizeFirstCharacter(alert.status.toLowerCase())"
class="gl-absolute gl-right-0"
right
>
<gl-dropdown-item
v-for="(label, field) in $options.statuses"
:key="field"
data-testid="statusDropdownItem"
class="gl-vertical-align-middle"
@click="updateAlertStatus(label)"
>
<span class="d-flex">
<gl-icon
class="flex-shrink-0 append-right-4"
:class="{ invisible: label.toUpperCase() !== alert.status }"
name="mobile-issue-close"
/>
{{ label }}
</span>
</gl-dropdown-item>
</gl-dropdown>
<gl-tabs v-if="alert" data-testid="alertDetailsTabs">
<gl-tab data-testid="overviewTab" :title="$options.i18n.overviewTitle">
<ul class="pl-4 mb-n1">

View File

@ -10,7 +10,6 @@ import {
GlDropdownItem,
GlTabs,
GlTab,
GlBadge,
} from '@gitlab/ui';
import createFlash from '~/flash';
import { s__ } from '~/locale';
@ -87,7 +86,6 @@ export default {
GlIcon,
GlTabs,
GlTab,
GlBadge,
},
mixins: [glFeatureFlagsMixin()],
props: {
@ -118,7 +116,7 @@ export default {
variables() {
return {
projectPath: this.projectPath,
status: this.statusFilter,
statuses: this.statusFilter,
};
},
update(data) {
@ -135,7 +133,7 @@ export default {
errored: false,
isAlertDismissed: false,
isErrorAlertDismissed: false,
statusFilter: this.$options.statusTabs[0].status,
statusFilter: this.$options.statusTabs[4].filters,
};
},
computed: {
@ -151,7 +149,7 @@ export default {
},
methods: {
filterALertsByStatus(tabIndex) {
this.statusFilter = this.$options.statusTabs[tabIndex].status;
this.statusFilter = this.$options.statusTabs[tabIndex].filters;
},
capitalizeFirstCharacter,
updateAlertStatus(status, iid) {
@ -190,9 +188,6 @@ export default {
<gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
<template slot="title">
<span>{{ tab.title }}</span>
<gl-badge v-if="alerts" size="sm" class="gl-tab-counter-badge">
{{ alerts.length }}
</gl-badge>
</template>
</gl-tab>
</gl-tabs>

View File

@ -10,32 +10,37 @@ export const ALERTS_SEVERITY_LABELS = {
};
export const ALERTS_STATUS = {
OPEN: 'open',
TRIGGERED: 'triggered',
ACKNOWLEDGED: 'acknowledged',
RESOLVED: 'resolved',
ALL: 'all',
OPEN: 'OPEN',
TRIGGERED: 'TRIGGERED',
ACKNOWLEDGED: 'ACKNOWLEDGED',
RESOLVED: 'RESOLVED',
ALL: 'ALL',
};
export const ALERTS_STATUS_TABS = [
{
title: s__('AlertManagement|Open'),
status: ALERTS_STATUS.OPEN,
filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED],
},
{
title: s__('AlertManagement|Triggered'),
status: ALERTS_STATUS.TRIGGERED,
filters: [ALERTS_STATUS.TRIGGERED],
},
{
title: s__('AlertManagement|Acknowledged'),
status: ALERTS_STATUS.ACKNOWLEDGED,
filters: [ALERTS_STATUS.ACKNOWLEDGED],
},
{
title: s__('AlertManagement|Resolved'),
status: ALERTS_STATUS.RESOLVED,
filters: [ALERTS_STATUS.RESOLVED],
},
{
title: s__('AlertManagement|All alerts'),
status: ALERTS_STATUS.ALL,
filters: [ALERTS_STATUS.TRIGGERED, ALERTS_STATUS.ACKNOWLEDGED, ALERTS_STATUS.RESOLVED],
},
];

View File

@ -1,6 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import AlertDetails from './components/alert_details.vue';
Vue.use(VueApollo);
@ -10,7 +11,20 @@ export default selector => {
const { alertId, projectPath, newIssuePath } = domEl.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
defaultClient: createDefaultClient(
{},
{
cacheConfig: {
dataIdFromObject: object => {
// eslint-disable-next-line no-underscore-dangle
if (object.__typename === 'AlertManagementAlert') {
return object.iid;
}
return defaultDataIdFromObject(object);
},
},
},
),
});
// eslint-disable-next-line no-new

View File

@ -1,8 +1,8 @@
#import "../fragments/listItem.fragment.graphql"
query getAlerts($projectPath: ID!) {
query getAlerts($projectPath: ID!, $statuses: [AlertManagementStatus!]) {
project(fullPath: $projectPath) {
alertManagementAlerts {
alertManagementAlerts(statuses: $statuses) {
nodes {
...AlertListItem
}

View File

@ -4,6 +4,7 @@ import flash from '~/flash';
import { __, sprintf, s__ } from '~/locale';
import { GlModal } from '@gitlab/ui';
import { modalTypes } from '../../constants';
import { trimPathComponents } from '../../utils';
export default {
components: {
@ -51,6 +52,8 @@ export default {
methods: {
...mapActions(['createTempEntry', 'renameEntry']),
submitForm() {
this.entryName = trimPathComponents(this.entryName);
if (this.modalType === modalTypes.rename) {
if (this.entries[this.entryName] && !this.entries[this.entryName].deleted) {
flash(

View File

@ -69,6 +69,12 @@ export const createPathWithExt = p => {
return `${p.substring(1, p.lastIndexOf('.') + 1 || p.length)}${ext || '.js'}`;
};
export const trimPathComponents = path =>
path
.split('/')
.map(s => s.trim())
.join('/');
export function registerLanguages(def, ...defs) {
if (defs.length) defs.forEach(lang => registerLanguages(lang));

View File

@ -1,3 +1,3 @@
import DueDateSelectors from '~/due_date_select';
import initExpiresAtField from '~/access_tokens';
document.addEventListener('DOMContentLoaded', () => new DueDateSelectors());
document.addEventListener('DOMContentLoaded', initExpiresAtField);

View File

@ -1,3 +1,3 @@
import DueDateSelectors from '~/due_date_select';
import initExpiresAtField from '~/access_tokens';
document.addEventListener('DOMContentLoaded', () => new DueDateSelectors());
document.addEventListener('DOMContentLoaded', initExpiresAtField);

View File

@ -0,0 +1,3 @@
import initExpiresAtField from '~/access_tokens';
document.addEventListener('DOMContentLoaded', initExpiresAtField);

View File

@ -17,7 +17,7 @@
min-height: 68px;
&:last-child {
background-color: $gray-normal;
background-color: $gray-10;
&::before {
content: none !important;

View File

@ -19,8 +19,6 @@ module Types
markdown_field :description_html, null: true
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the release'
field :evidence_sha, GraphQL::STRING_TYPE, null: true,
description: "SHA of the release's evidence"
field :created_at, Types::TimeType, null: true,
description: 'Timestamp of when the release was created'
field :released_at, Types::TimeType, null: true,

View File

@ -674,6 +674,7 @@ module ProjectsHelper
services#edit
hooks#index
hooks#edit
access_tokens#index
hook_logs#show
repository#show
ci_cd#show

View File

@ -53,7 +53,6 @@ module Timebox
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_groups, ->(ids) { where(group_id: ids) }
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
scope :for_projects, -> { where(group: nil).includes(:project) }
scope :with_title, -> (title) { where(title: title) }

View File

@ -84,6 +84,7 @@ class Issue < ApplicationRecord
scope :preload_associated_models, -> { preload(:assignees, :labels, project: :namespace) }
scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) }
scope :with_label_attributes, ->(label_attributes) { joins(:labels).where(labels: label_attributes) }
scope :public_only, -> { where(confidential: false) }
scope :confidential_only, -> { where(confidential: true) }

View File

@ -5,9 +5,12 @@ class Iteration < ApplicationRecord
self.table_name = 'sprints'
STATE_ID_MAP = {
active: 1,
closed: 2
attr_accessor :skip_future_date_validation
STATE_ENUM_MAP = {
upcoming: 1,
started: 2,
closed: 3
}.with_indifferent_access.freeze
include AtomicInternalId
@ -21,16 +24,77 @@ class Iteration < ApplicationRecord
has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.iterations&.maximum(:iid) }
has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.iterations&.maximum(:iid) }
state_machine :state, initial: :active do
validates :start_date, presence: true
validates :due_date, presence: true
validate :dates_do_not_overlap, if: :start_or_due_dates_changed?
validate :future_date, if: :start_or_due_dates_changed?, unless: :skip_future_date_validation
scope :upcoming, -> { with_state(:upcoming) }
scope :started, -> { with_state(:started) }
state_machine :state_enum, initial: :upcoming do
event :start do
transition upcoming: :started
end
event :close do
transition active: :closed
transition [:upcoming, :started] => :closed
end
event :activate do
transition closed: :active
state :upcoming, value: Iteration::STATE_ENUM_MAP[:upcoming]
state :started, value: Iteration::STATE_ENUM_MAP[:started]
state :closed, value: Iteration::STATE_ENUM_MAP[:closed]
end
# Alias to state machine .with_state_enum method
# This needs to be defined after the state machine block to avoid errors
class << self
alias_method :with_state, :with_state_enum
alias_method :with_states, :with_state_enums
def filter_by_state(iterations, state)
case state
when 'closed' then iterations.closed
when 'started' then iterations.started
when 'opened' then iterations.started.or(iterations.upcoming)
when 'all' then iterations
else iterations.upcoming
end
end
end
def state
STATE_ENUM_MAP.key(state_enum)
end
def state=(value)
self.state_enum = STATE_ENUM_MAP[value]
end
private
def start_or_due_dates_changed?
start_date_changed? || due_date_changed?
end
# ensure dates do not overlap with other Iterations in the same group/project
def dates_do_not_overlap
return unless resource_parent.iterations.within_timeframe(start_date, due_date).exists?
errors.add(:base, s_("Iteration|Dates cannot overlap with other existing Iterations"))
end
# ensure dates are in the future
def future_date
if start_date_changed?
errors.add(:start_date, s_("Iteration|cannot be in the past")) if start_date < Date.today
errors.add(:start_date, s_("Iteration|cannot be more than 500 years in the future")) if start_date > 500.years.from_now
end
state :active, value: Iteration::STATE_ID_MAP[:active]
state :closed, value: Iteration::STATE_ID_MAP[:closed]
if due_date_changed?
errors.add(:due_date, s_("Iteration|cannot be in the past")) if due_date < Date.today
errors.add(:due_date, s_("Iteration|cannot be more than 500 years in the future")) if due_date > 500.years.from_now
end
end
end

View File

@ -18,6 +18,7 @@ class Milestone < ApplicationRecord
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
scope :active, -> { with_state(:active) }
scope :started, -> { active.where('milestones.start_date <= CURRENT_DATE') }
scope :not_started, -> { active.where('milestones.start_date > CURRENT_DATE') }
scope :not_upcoming, -> do

View File

@ -52,4 +52,7 @@ class ProjectRepositoryStorageMove < ApplicationRecord
state :finished, value: 4
state :failed, value: 5
end
scope :order_created_at_desc, -> { order(created_at: :desc) }
scope :with_projects, -> { includes(project: :route) }
end

View File

@ -81,14 +81,6 @@ class Release < ApplicationRecord
self.milestones.map {|m| m.title }.sort.join(", ")
end
def evidence_sha
evidences.first&.summary_sha
end
def evidence_summary
evidences.first&.summary || {}
end
private
def actual_sha

View File

@ -43,13 +43,6 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
edit_project_release_url(project, release)
end
def evidence_file_path
evidence = release.evidences.first
return unless evidence
project_evidence_url(project, release, evidence, format: :json)
end
private
def can_download_code?

View File

@ -15,7 +15,7 @@ class FileUploader < GitlabUploader
prepend ObjectStorage::Extension::RecordsUploads
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze
DYNAMIC_PATH_PATTERN = %r{.*/(?<secret>\h{10,32})/(?<identifier>.*)}.freeze
DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\b(\h{10}|\h{32}))\/(?<identifier>.*)}.freeze
VALID_SECRET_PATTERN = %r{\A\h{10,32}\z}.freeze
InvalidSecret = Class.new(StandardError)

View File

@ -1,15 +1,29 @@
- add_to_breadcrumbs "Users", admin_users_path
- add_to_breadcrumbs 'Users', admin_users_path
- breadcrumb_title @user.name
- page_title "Impersonation Tokens", @user.name, "Users"
- page_title _('Impersonation Tokens'), @user.name, _('Users')
- type = _('impersonation token')
- type_plural = _('impersonation tokens')
= render 'admin/users/head'
.row.prepend-top-default
.col-lg-12
- if @new_impersonation_token
= render "shared/personal_access_tokens_created_container", new_token_value: @new_impersonation_token,
container_title: 'Your New Impersonation Token',
clipboard_button_title: _('Copy impersonation token')
= render 'shared/access_tokens/created_container',
type: type,
new_token_value: @new_impersonation_token
= render "shared/personal_access_tokens_form", path: admin_user_impersonation_tokens_path, impersonation: true, token: @impersonation_token, scopes: @scopes
= render 'shared/access_tokens/form',
type: type,
title: _('Add an impersonation token'),
path: admin_user_impersonation_tokens_path,
impersonation: true,
token: @impersonation_token,
scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: true, active_tokens: @active_impersonation_tokens, inactive_tokens: @inactive_impersonation_tokens
= render 'shared/access_tokens/table',
type: type,
type_plural: type_plural,
impersonation: true,
active_tokens: @active_impersonation_tokens,
revoke_route_helper: ->(token) { revoke_admin_user_impersonation_token_path(token.user, token) }

View File

@ -363,6 +363,11 @@
= link_to project_hooks_path(@project), title: _('Webhooks'), data: { qa_selector: 'webhooks_settings_link' } do
%span
= _('Webhooks')
- if project_access_token_available?(@project)
= nav_link(controller: [:access_tokens]) do
= link_to project_settings_access_tokens_path(@project), title: _('Access Tokens'), data: { qa_selector: 'access_tokens_settings_link' } do
%span
= _('Access Tokens')
= nav_link(controller: :repository) do
= link_to project_settings_repository_path(@project), title: _('Repository') do
%span

View File

@ -1,6 +1,8 @@
- breadcrumb_title s_('AccessTokens|Access Tokens')
- page_title s_('AccessTokens|Personal Access Tokens')
- @content_class = "limit-container-width" unless fluid_layout
- type = _('personal access token')
- type_plural = _('personal access tokens')
- @content_class = 'limit-container-width' unless fluid_layout
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
@ -14,11 +16,21 @@
.col-lg-8
- if @new_personal_access_token
= render "shared/personal_access_tokens_created_container", new_token_value: @new_personal_access_token
= render 'shared/access_tokens/created_container',
type: type,
new_token_value: @new_personal_access_token
= render "shared/personal_access_tokens_form", path: profile_personal_access_tokens_path, impersonation: false, token: @personal_access_token, scopes: @scopes
= render 'shared/access_tokens/form',
type: type,
path: profile_personal_access_tokens_path,
token: @personal_access_token,
scopes: @scopes
= render "shared/personal_access_tokens_table", impersonation: false, active_tokens: @active_personal_access_tokens, inactive_tokens: @inactive_personal_access_tokens
= render 'shared/access_tokens/table',
type: type,
type_plural: type_plural,
active_tokens: @active_personal_access_tokens,
revoke_route_helper: ->(token) { revoke_profile_personal_access_token_path(token) }
%hr
.row.prepend-top-default
@ -30,7 +42,7 @@
%p
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.feed-token-reset
= label_tag :feed_token, s_('AccessTokens|Feed token'), class: "label-bold"
= label_tag :feed_token, s_('AccessTokens|Feed token'), class: 'label-bold'
= text_field_tag :feed_token, current_user.feed_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.') }
@ -48,7 +60,7 @@
%p
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.incoming-email-token-reset
= label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: "label-bold"
= label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: 'label-bold'
= text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.') }

View File

@ -0,0 +1,34 @@
- breadcrumb_title s_('AccessTokens|Access Tokens')
- page_title _('Project Access Tokens')
- type = _('project access token')
- type_plural = _('project access tokens')
- @content_class = 'limit-container-width' unless fluid_layout
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= page_title
%p
= _('You can generate an access token scoped to this project for each application to use the GitLab API.')
%p
= _('You can also use project access tokens to authenticate against Git over HTTP.')
.col-lg-8
- if @new_project_access_token
= render 'shared/access_tokens/created_container',
type: type,
new_token_value: @new_project_access_token
= render 'shared/access_tokens/form',
type: type,
path: project_settings_access_tokens_path(@project),
token: @project_access_token,
scopes: @scopes,
prefix: :project_access_token
= render 'shared/access_tokens/table',
active_tokens: @active_project_access_tokens,
type: type,
type_plural: type_plural,
revoke_route_helper: ->(token) { revoke_namespace_project_settings_access_token_path(id: token) },
no_active_tokens_message: _('This project has no active access tokens.')

View File

@ -13,7 +13,7 @@
%ul.label-links
- if show_label_issues_link
%li.label-link-item.inline
= link_to_label(label) { 'Issues' }
= link_to_label(label) { _('Issues') }
- if show_label_merge_requests_link
&middot;
%li.label-link-item.inline

View File

@ -1,13 +1,13 @@
%ul.nav-links.mobile-separator.nav.nav-tabs
%li{ class: milestone_class_for_state(params[:state], 'opened', true) }>
= link_to milestones_filter_path(state: 'opened') do
Open
= _('Open')
%span.badge.badge-pill= counts[:opened]
%li{ class: milestone_class_for_state(params[:state], 'closed') }>
= link_to milestones_filter_path(state: 'closed', sort: 'due_date_desc') do
Closed
= _('Closed')
%span.badge.badge-pill= counts[:closed]
%li{ class: milestone_class_for_state(params[:state], 'all') }>
= link_to milestones_filter_path(state: 'all', sort: 'due_date_desc') do
All
= _('All')
%span.badge.badge-pill= counts[:all]

View File

@ -1,15 +0,0 @@
- container_title = local_assigns.fetch(:container_title, _('Your New Personal Access Token'))
- clipboard_button_title = local_assigns.fetch(:clipboard_button_title, _('Copy personal access token'))
.created-personal-access-token-container
%h5.prepend-top-0
= container_title
.form-group
.input-group
= text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "qa-created-personal-access-token form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block"
%span.input-group-append
= clipboard_button(text: new_token_value, title: clipboard_button_title, placement: "left", class: "input-group-text btn-default btn-clipboard")
%span#created-token-help-block.form-text.text-muted.text-danger
= _("Make sure you save it - you won't be able to access it again.")
%hr

View File

@ -1,31 +0,0 @@
- type = impersonation ? s_('Profiles|impersonation') : s_('Profiles|personal access')
%h5.prepend-top-0
= _('Add a %{type} token') % { type: type }
%p.profile-settings-content
= _("Pick a name for the application, and we'll give you a unique %{type} token.") % { type: type }
= form_for token, url: path, method: :post, html: { class: 'js-requires-input' } do |f|
= form_errors(token)
.row
.form-group.col-md-6
= f.label :name, _('Name'), class: 'label-bold'
= f.text_field :name, class: "form-control", required: true, data: { qa_selector: 'personal_access_token_name_field' }
.row
.form-group.col-md-6
= f.label :expires_at, _('Expires at'), class: 'label-bold'
.input-icon-wrapper
= render_if_exists 'personal_access_tokens/callout_max_personal_access_token_lifetime'
= f.text_field :expires_at, class: "datepicker form-control", placeholder: 'YYYY-MM-DD', data: { qa_selector: 'expiry_date_field' }
.form-group
= f.label :scopes, _('Scopes'), class: 'label-bold'
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes
.prepend-top-default
= f.submit _('Create %{type} token') % { type: type }, class: "btn btn-success", data: { qa_selector: 'create_token_button' }

View File

@ -1,8 +1,8 @@
- if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project? && current_user.projects_limit > 0
.project-limit-message.alert.alert-warning.d-none.d-sm-block
You won't be able to create new projects because you have reached your project limit.
= _("You won't be able to create new projects because you have reached your project limit.")
.float-right
= link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link'
= link_to _("Don't show again"), profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link'
|
= link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link'
= link_to _('Remind later'), '#', class: 'hide-project-limit-message alert-link'

View File

@ -0,0 +1,12 @@
.created-personal-access-token-container
%h5.prepend-top-0
= _('Your new %{type}') % { type: type }
.form-group
.input-group
= text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: 'qa-created-access-token form-control js-select-on-focus', 'aria-describedby' => 'created-token-help-block'
%span.input-group-append
= clipboard_button(text: new_token_value, title: _('Copy %{type}') % { type: type }, placement: 'left', class: 'input-group-text btn-default btn-clipboard')
%span#created-token-help-block.form-text.text-muted.text-danger
= _("Make sure you save it - you won't be able to access it again.")
%hr

View File

@ -0,0 +1,34 @@
- title = local_assigns.fetch(:title, _('Add a %{type}') % { type: type })
- prefix = local_assigns.fetch(:prefix, :personal_access_token)
%h5.prepend-top-0
= title
%p.profile-settings-content
= _("Enter the name of your application, and we'll return a unique %{type}.") % { type: type }
= form_for token, as: prefix, url: path, method: :post, html: { class: 'js-requires-input' } do |f|
= form_errors(token)
.row
.form-group.col-md-6
= f.label :name, _('Name'), class: 'label-bold'
= f.text_field :name, class: 'form-control', required: true, data: { qa_selector: 'access_token_name_field' }
.row
.form-group.col-md-6
= f.label :expires_at, _('Expires at'), class: 'label-bold'
.input-icon-wrapper
= render_if_exists 'personal_access_tokens/callout_max_personal_access_token_lifetime'
.js-access-tokens-expires-at
%expires-at-field
= f.text_field :expires_at, class: 'datepicker form-control gl-datepicker-input', placeholder: 'YYYY-MM-DD', autocomplete: 'off', inputmode: 'none', data: { qa_selector: 'expiry_date_field' }
.form-group
= f.label :scopes, _('Scopes'), class: 'label-bold'
= render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes
.prepend-top-default
= f.submit _('Create %{type}') % { type: type }, class: 'btn btn-success', data: { qa_selector: 'create_token_button' }

View File

@ -1,8 +1,10 @@
- type = impersonation ? s_('Profiles|Impersonation') : s_('Profiles|Personal Access')
- no_active_tokens_message = local_assigns.fetch(:no_active_tokens_message, _('This user has no active %{type}.') % { type: type_plural })
- impersonation = local_assigns.fetch(:impersonation, false)
%hr
%h5
= _('Active %{type} Tokens (%{token_length})') % { type: type, token_length: active_tokens.length }
= _('Active %{type} (%{token_length})') % { type: type_plural, token_length: active_tokens.length }
- if impersonation
%p.profile-settings-content
= _("To see all the user's personal access tokens you must impersonate them first.")
@ -28,9 +30,8 @@
In #{distance_of_time_in_words_to_now(token.expires_at)}
- else
%span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(", ") : _('<no scopes selected>')
- path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token)
%td= link_to _('Revoke'), path, method: :put, class: "btn btn-danger float-right qa-revoke-button", data: { confirm: _('Are you sure you want to revoke this %{type} Token? This action cannot be undone.') % { type: type } }
%td= token.scopes.present? ? token.scopes.join(', ') : _('<no scopes selected>')
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
- else
.settings-message.text-center
= _('This user has no active %{type} Tokens.') % { type: type }
= no_active_tokens_message

View File

@ -8,14 +8,14 @@
- if is_current_user
- if can_update
= link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: "Close #{display_issuable_type}", data: { qa_selector: 'close_issue_button' }
= link_to _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, close_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, data: { qa_selector: 'close_issue_button' }
- if can_reopen
= link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}", data: { qa_selector: 'reopen_issue_button' }
= link_to _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, reopen_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, data: { qa_selector: 'reopen_issue_button' }
- else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable, warn_before_close: add_blocked_class
- else
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: 'Report abuse'
= link_to _('Report abuse'), new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: _('Report abuse')

View File

@ -14,39 +14,37 @@
method: button_method, class: "#{button_class} btn-#{button_action} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: "#{display_button_action} #{display_issuable_type}", data: { qa_selector: 'close_issue_button' }
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => _('Toggle dropdown') do
= icon('caret-down', class: 'toggle-icon icon')
%ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ data: { dropdown: true } }
%li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}",
data: { text: "Close #{display_issuable_type}", url: close_issuable_path(issuable),
data: { text: _("Close %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, url: close_issuable_path(issuable),
button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title
Close
= _('Close')
= display_issuable_type
%li.reopen-item{ class: "#{issuable_button_visibility(issuable, false) || 'droplab-item-selected'}",
data: { text: "Reopen #{display_issuable_type}", url: reopen_issuable_path(issuable),
data: { text: _("Reopen %{display_issuable_type}") % { display_issuable_type: display_issuable_type }, url: reopen_issuable_path(issuable),
button_class: "#{button_class} btn-reopen", toggle_class: "#{toggle_class} btn-reopen-color", method: button_method } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title
Reopen
= _('Reopen')
= display_issuable_type
%li.divider.droplab-item-ignore
%li.report-item{ data: { text: 'Report abuse', url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
%li.report-item{ data: { text: _('Report abuse'), url: new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
button_class: "#{button_class} btn-close-color", toggle_class: "#{toggle_class} btn-close-color", method: '' } }
%button.btn.btn-transparent
= icon('check', class: 'icon')
.description
%strong.title Report abuse
%strong.title = _('Report abuse')
%p.text
Report
= display_issuable_type.pluralize
that are abusive, inappropriate or spam.
= _('Report %{display_issuable_type} that are abusive, inappropriate or spam.') % { display_issuable_type: display_issuable_type.pluralize }

View File

@ -67,7 +67,7 @@
class: 'btn btn-default align-self-center mr-sm-2',
title: _('Resend invite')
- if user != current_user && member.can_update?
- if user != current_user && member.can_update? && !user&.project_bot?
= form_for member, remote: true, html: { class: "js-edit-member-form form-group #{'d-sm-flex' unless force_mobile_view}" } do |f|
= f.hidden_field :access_level
.member-form-control.dropdown{ class: [("mr-sm-2 d-sm-inline-block" unless force_mobile_view)] }
@ -117,7 +117,7 @@
method: :delete,
data: { confirm: leave_confirmation_message(member.source) },
class: "btn btn-remove align-self-center m-0 #{'ml-sm-2' unless force_mobile_view}"
- else
- elsif !user&.project_bot?
= link_to member,
method: :delete,
data: { confirm: remove_member_message(member), qa_selector: 'delete_member_button' },

View File

@ -0,0 +1,5 @@
---
title: Remove JenkinsDeprecatedService
merge_request: 31607
author: tnwx
type: removed

View File

@ -0,0 +1,6 @@
---
title: Deploy tokens can be used in the API with Basic Auth Headers enabling NuGet
and PyPI to be used with deploy tokens
merge_request: 31035
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Remove deprecated Release Evidence endpoints
merge_request: 30975
author:
type: removed

View File

@ -0,0 +1,5 @@
---
title: Add incident_labeled_issues to usage ping
merge_request: 31406
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Trim whitespace in directory names in the Web IDE
merge_request: 31305
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix incorrect regex used in FileUploader#extract_dynamic_path
merge_request: 32271
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from ./app/views/shared/issuable/_close_reopen_report_toggle.html.haml
merge_request: 32168
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from ./app/views/shared/issuable/_close_reopen_button.html.haml
merge_request: 32172
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from ./app/views/shared/_label_row.html.haml
merge_request: 32124
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from ./app/views/shared/_milestones_filter.html.haml
merge_request: 32120
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Externalize i18n strings from ./app/views/shared/_project_limit.html.haml
merge_request: 32110
author: Gilang Gumilar
type: changed

View File

@ -0,0 +1,5 @@
---
title: Update android template
merge_request: 32096
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Update error tracking table background colour to correct gray
merge_request: 32133
author:
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix leaky constant issue in sidekiq middleware spec
merge_request: 32101
author: Rajendra Kadam
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Read only storage move API
merge_request: 31285
author:
type: added

View File

@ -10,7 +10,7 @@ development:
# host: localhost
# port: 26380 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26380 # point to sentinel, not to redis port
test:
url: redis://localhost:6379/10
@ -31,8 +31,8 @@ production:
# url: redis://master:6380
# sentinels:
# -
# host: slave1
# host: replica1
# port: 26380 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26380 # point to sentinel, not to redis port

View File

@ -10,7 +10,7 @@ development:
# host: localhost
# port: 26381 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26381 # point to sentinel, not to redis port
test:
url: redis://localhost:6379/11
@ -31,8 +31,8 @@ production:
# url: redis://master:6381
# sentinels:
# -
# host: slave1
# host: replica1
# port: 26381 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26381 # point to sentinel, not to redis port

View File

@ -10,7 +10,7 @@ development:
# host: localhost
# port: 26382 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26382 # point to sentinel, not to redis port
test:
url: redis://localhost:6379/12
@ -31,8 +31,8 @@ production:
# url: redis://master:6382
# sentinels:
# -
# host: slave1
# host: replica1
# port: 26382 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26382 # point to sentinel, not to redis port

View File

@ -8,7 +8,7 @@ development:
# host: localhost
# port: 26380 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26381 # point to sentinel, not to redis port
test:
url: redis://localhost:6379
@ -27,8 +27,8 @@ production:
# url: redis://master:6379
# sentinels:
# -
# host: slave1
# host: replica1
# port: 26379 # point to sentinel, not to redis port
# -
# host: slave2
# host: replica2
# port: 26379 # point to sentinel, not to redis port

View File

@ -484,7 +484,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# Legacy routes.
# Introduced in 12.0.
# Should be removed with https://gitlab.com/gitlab-org/gitlab/issues/28848.
Gitlab::Routing.redirect_legacy_paths(self, :mirror,
Gitlab::Routing.redirect_legacy_paths(self, :mirror, :tags,
:cycle_analytics, :mattermost, :variables, :triggers,
:environments, :protected_environments, :error_tracking, :alert_management,
:serverless, :clusters, :audit_events, :wikis, :merge_requests,

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class SprintRenameStateToStateEnum < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
rename_column_concurrently :sprints, :state, :state_enum
end
def down
undo_rename_column_concurrently :sprints, :state, :state_enum
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class RemoveDeprecatedJenkinsServiceRecords < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
execute <<~SQL.strip
DELETE FROM services WHERE type = 'JenkinsDeprecatedService';
SQL
end
def down
# no-op
# The records were removed by `up`
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
class SprintMakeStateEnumNotNullAndDefault < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
change_column_default :sprints, :state_enum, from: 0, to: 1
change_column_null :sprints, :state_enum, false, 1
end
def down
change_column_null :sprints, :state_enum, true
change_column_default :sprints, :state_enum, from: 1, to: nil
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class CleanupSprintsStateRename < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :sprints, :state, :state_enum
end
def down
undo_cleanup_concurrent_column_rename :sprints, :state, :state_enum
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
class EnsureDeprecatedJenkinsServiceRecordsRemoval < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
execute <<~SQL.strip
DELETE FROM services WHERE type = 'JenkinsDeprecatedService';
SQL
end
def down
# no-op
# The records were removed by `up`
end
end

View File

@ -6242,11 +6242,11 @@ CREATE TABLE public.sprints (
group_id bigint,
iid integer NOT NULL,
cached_markdown_version integer,
state smallint,
title text NOT NULL,
title_html text,
description text,
description_html text,
state_enum smallint DEFAULT 1 NOT NULL,
CONSTRAINT sprints_must_belong_to_project_or_group CHECK ((((project_id <> NULL::bigint) AND (group_id IS NULL)) OR ((group_id <> NULL::bigint) AND (project_id IS NULL)))),
CONSTRAINT sprints_title CHECK ((char_length(title) <= 255))
);
@ -13789,6 +13789,8 @@ COPY "schema_migrations" (version) FROM STDIN;
20200424101920
20200424135319
20200427064130
20200429001827
20200429002150
20200429015603
20200429181335
20200429181955
@ -13808,10 +13810,13 @@ COPY "schema_migrations" (version) FROM STDIN;
20200511121549
20200511121610
20200511121620
20200511130129
20200511130130
20200511145545
20200511162057
20200511162115
20200512085150
20200512164334
20200513234502
20200513235347
20200513235532

View File

@ -98,7 +98,7 @@ Starting with 8.14, Redis Sentinel is no longer experimental.
If you've used it with versions `< 8.14` before, please check the updated
documentation here.
High Availability with [Redis](https://redis.io/) is possible using a **Master** x **Slave**
High Availability with [Redis](https://redis.io/) is possible using a **Master** x **Replica**
topology with a [Redis Sentinel](https://redis.io/topics/sentinel) service to watch and automatically
start the failover procedure.
@ -130,12 +130,12 @@ make sure you read this Overview section to better understand how the components
are tied together.
You need at least `3` independent machines: physical, or VMs running into
distinct physical machines. It is essential that all master and slaves Redis
distinct physical machines. It is essential that all master and replica Redis
instances run in different machines. If you fail to provision the machines in
that specific way, any issue with the shared environment can bring your entire
setup down.
It is OK to run a Sentinel alongside of a master or slave Redis instance.
It is OK to run a Sentinel alongside of a master or replica Redis instance.
There should be no more than one Sentinel on the same machine though.
You also need to take into consideration the underlying network topology,
@ -156,16 +156,16 @@ components below.
High Availability with Redis requires a few things:
- Multiple Redis instances
- Run Redis in a **Master** x **Slave** topology
- Run Redis in a **Master** x **Replica** topology
- Multiple Sentinel instances
- Application support and visibility to all Sentinel and Redis instances
Redis Sentinel can handle the most important tasks in an HA environment and that's
to help keep servers online with minimal to no downtime. Redis Sentinel:
- Monitors **Master** and **Slaves** instances to see if they are available
- Promotes a **Slave** to **Master** when the **Master** fails
- Demotes a **Master** to **Slave** when the failed **Master** comes back online
- Monitors **Master** and **Replicas** instances to see if they are available
- Promotes a **Replica** to **Master** when the **Master** fails
- Demotes a **Master** to **Replica** when the failed **Master** comes back online
(to prevent data-partitioning)
- Can be queried by the application to always connect to the current **Master**
server
@ -185,8 +185,8 @@ For a minimal setup, you will install the Omnibus GitLab package in `3`
**independent** machines, both with **Redis** and **Sentinel**:
- Redis Master + Sentinel
- Redis Slave + Sentinel
- Redis Slave + Sentinel
- Redis Replica + Sentinel
- Redis Replica + Sentinel
If you are not sure or don't understand why and where the amount of nodes come
from, read [Redis setup overview](#redis-setup-overview) and
@ -197,14 +197,14 @@ the Omnibus GitLab package in `5` **independent** machines, both with
**Redis** and **Sentinel**:
- Redis Master + Sentinel
- Redis Slave + Sentinel
- Redis Slave + Sentinel
- Redis Slave + Sentinel
- Redis Slave + Sentinel
- Redis Replica + Sentinel
- Redis Replica + Sentinel
- Redis Replica + Sentinel
- Redis Replica + Sentinel
### Redis setup overview
You must have at least `3` Redis servers: `1` Master, `2` Slaves, and they
You must have at least `3` Redis servers: `1` Master, `2` Replicas, and they
need to each be on independent machines (see explanation above).
You can have additional Redis nodes, that will help survive a situation
@ -221,7 +221,7 @@ nodes to be provisioned. See [Sentinel setup overview](#sentinel-setup-overview)
documentation for more information.
All Redis nodes should be configured the same way and with similar server specs, as
in a failover situation, any **Slave** can be promoted as the new **Master** by
in a failover situation, any **Replica** can be promoted as the new **Master** by
the Sentinel servers.
The replication requires authentication, so you need to define a password to
@ -241,9 +241,9 @@ need to be available and reachable, so that they can elect the Sentinel **leader
who will take all the decisions to restore the service availability by:
- Promoting a new **Master**
- Reconfiguring the other **Slaves** and make them point to the new **Master**
- Reconfiguring the other **Replicas** and make them point to the new **Master**
- Announce the new **Master** to every other Sentinel peer
- Reconfigure the old **Master** and demote to **Slave** when it comes back online
- Reconfigure the old **Master** and demote to **Replica** when it comes back online
You must have at least `3` Redis Sentinel servers, and they need to
be each in an independent machine (that are believed to fail independently),
@ -280,18 +280,18 @@ the official documentation:
already tried against the same master by a given Sentinel, is two
times the failover timeout.
- The time needed for a slave replicating to a wrong master according
- The time needed for a replica replicating to a wrong master according
to a Sentinel current configuration, to be forced to replicate
with the right master, is exactly the failover timeout (counting since
the moment a Sentinel detected the misconfiguration).
- The time needed to cancel a failover that is already in progress but
did not produced any configuration change (SLAVEOF NO ONE yet not
acknowledged by the promoted slave).
did not produced any configuration change (REPLICAOF NO ONE yet not
acknowledged by the promoted replica).
- The maximum time a failover in progress waits for all the slaves to be
reconfigured as slaves of the new master. However even after this time
the slaves will be reconfigured by the Sentinels anyway, but not with
- The maximum time a failover in progress waits for all the replicas to be
reconfigured as replicas of the new master. However even after this time
the replicas will be reconfigured by the Sentinels anyway, but not with
the exact parallel-syncs progression as specified.
### Available configuration setups
@ -306,12 +306,12 @@ Pick the one that suits your needs.
documentation.
- [Omnibus GitLab **Community Edition** (CE) package](https://about.gitlab.com/install/?version=ce): Redis is bundled, so you
can use the package with only the Redis service enabled as described in steps
1 and 2 of this document (works for both master and slave setups). To install
1 and 2 of this document (works for both master and replica setups). To install
and configure Sentinel, jump directly to the Sentinel section in the
[Redis HA installation from source](redis_source.md#step-3-configuring-the-redis-sentinel-instances) documentation.
- [Omnibus GitLab **Enterprise Edition** (EE) package](https://about.gitlab.com/install/?version=ee): Both Redis and Sentinel
are bundled in the package, so you can use the EE package to set up the whole
Redis HA infrastructure (master, slave and Sentinel) which is described in
Redis HA infrastructure (master, replica and Sentinel) which is described in
this document.
- If you have installed GitLab using the Omnibus GitLab packages (CE or EE),
but you want to use your own external Redis server, follow steps 1-3 in the
@ -328,9 +328,9 @@ This is the section where we install and set up the new Redis instances.
> - We assume that you have installed GitLab and all HA components from scratch. If you
> already have it installed and running, read how to
> [switch from a single-machine installation to Redis HA](#switching-from-an-existing-single-machine-installation-to-redis-ha).
> - Redis nodes (both master and slaves) will need the same password defined in
> - Redis nodes (both master and replica) will need the same password defined in
> `redis['password']`. At any time during a failover the Sentinels can
> reconfigure a node and change its status from master to slave and vice versa.
> reconfigure a node and change its status from master to replica and vice versa.
### Prerequisites
@ -392,9 +392,9 @@ The prerequisites for a HA Redis setup are the following:
> `roles ['redis_sentinel_role', 'redis_master_role']`. Read more about high
> availability roles at <https://docs.gitlab.com/omnibus/roles/>.
### Step 2. Configuring the slave Redis instances
### Step 2. Configuring the replica Redis instances
1. SSH into the **slave** Redis server.
1. SSH into the **replica** Redis server.
1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
package you want using **steps 1 and 2** from the GitLab downloads page.
- Make sure you select the correct Omnibus package, with the same version
@ -404,8 +404,8 @@ The prerequisites for a HA Redis setup are the following:
1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
```ruby
# Specify server role as 'redis_slave_role'
roles ['redis_slave_role']
# Specify server role as 'redis_replica_role'
roles ['redis_replica_role']
# IP address pointing to a local IP that the other machines can reach to.
# You can also set bind to '0.0.0.0' which listen in all interfaces.
@ -435,10 +435,10 @@ The prerequisites for a HA Redis setup are the following:
```
1. [Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
1. Go through the steps again for all the other slave nodes.
1. Go through the steps again for all the other replica nodes.
> Note: You can specify multiple roles like sentinel and Redis as:
> `roles ['redis_sentinel_role', 'redis_slave_role']`. Read more about high
> `roles ['redis_sentinel_role', 'redis_replica_role']`. Read more about high
> availability roles at <https://docs.gitlab.com/omnibus/roles/>.
---
@ -542,18 +542,18 @@ multiple machines with the Sentinel daemon.
## already tried against the same master by a given Sentinel, is two
## times the failover timeout.
##
## - The time needed for a slave replicating to a wrong master according
## - The time needed for a replica replicating to a wrong master according
## to a Sentinel current configuration, to be forced to replicate
## with the right master, is exactly the failover timeout (counting since
## the moment a Sentinel detected the misconfiguration).
##
## - The time needed to cancel a failover that is already in progress but
## did not produced any configuration change (SLAVEOF NO ONE yet not
## acknowledged by the promoted slave).
## did not produced any configuration change (REPLICAOF NO ONE yet not
## acknowledged by the promoted replica).
##
## - The maximum time a failover in progress waits for all the slaves to be
## reconfigured as slaves of the new master. However even after this time
## the slaves will be reconfigured by the Sentinels anyway, but not with
## - The maximum time a failover in progress waits for all the replica to be
## reconfigured as replicas of the new master. However even after this time
## the replicas will be reconfigured by the Sentinels anyway, but not with
## the exact parallel-syncs progression as specified.
# sentinel['failover_timeout'] = 60000
```
@ -612,7 +612,7 @@ replicate from this machine first, before de-activating the Redis instance
inside it.
Your single-machine install will be the initial **Master**, and the `3` others
should be configured as **Slave** pointing to this machine.
should be configured as **Replica** pointing to this machine.
After replication catches up, you will need to stop services in the
single-machine install, to rotate the **Master** to one of the new nodes.
@ -627,7 +627,7 @@ redis['enable'] = false
If you fail to replicate first, you may loose data (unprocessed background jobs).
## Example of a minimal configuration with 1 master, 2 slaves and 3 Sentinels
## Example of a minimal configuration with 1 master, 2 replicas and 3 Sentinels
>**Note:**
Redis Sentinel is bundled with Omnibus GitLab Enterprise Edition only. For
@ -649,8 +649,8 @@ discussed in [Redis setup overview](#redis-setup-overview) and
Here is a list and description of each **machine** and the assigned **IP**:
- `10.0.0.1`: Redis Master + Sentinel 1
- `10.0.0.2`: Redis Slave 1 + Sentinel 2
- `10.0.0.3`: Redis Slave 2 + Sentinel 3
- `10.0.0.2`: Redis Replica 1 + Sentinel 2
- `10.0.0.3`: Redis Replica 2 + Sentinel 3
- `10.0.0.4`: GitLab application
Please note that after the initial configuration, if a failover is initiated
@ -684,12 +684,12 @@ sentinel['quorum'] = 2
[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
### Example configuration for Redis slave 1 and Sentinel 2
### Example configuration for Redis replica 1 and Sentinel 2
In `/etc/gitlab/gitlab.rb`:
```ruby
roles ['redis_sentinel_role', 'redis_slave_role']
roles ['redis_sentinel_role', 'redis_replica_role']
redis['bind'] = '10.0.0.2'
redis['port'] = 6379
redis['password'] = 'redis-password-goes-here'
@ -706,12 +706,12 @@ sentinel['quorum'] = 2
[Reconfigure Omnibus GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
### Example configuration for Redis slave 2 and Sentinel 3
### Example configuration for Redis replica 2 and Sentinel 3
In `/etc/gitlab/gitlab.rb`:
```ruby
roles ['redis_sentinel_role', 'redis_slave_role']
roles ['redis_sentinel_role', 'redis_replica_role']
redis['bind'] = '10.0.0.3'
redis['port'] = 6379
redis['password'] = 'redis-password-goes-here'
@ -847,11 +847,11 @@ mailroom['enable'] = false
-------
## Redis master/slave Role
## Redis master/replica Role
redis_master_role['enable'] = true # enable only one of them
redis_slave_role['enable'] = true # enable only one of them
redis_replica_role['enable'] = true # enable only one of them
# When Redis Master or Slave role are enabled, the following services are
# When Redis Master or Replica role are enabled, the following services are
# enabled/disabled. Note that if Redis and Sentinel roles are combined, both
# services will be enabled.
@ -863,7 +863,7 @@ postgresql['enable'] = false
gitlab_rails['enable'] = false
mailroom['enable'] = false
# For Redis Slave role, also change this setting from default 'true' to 'false':
# For Redis Replica role, also change this setting from default 'true' to 'false':
redis['master'] = false
```
@ -894,13 +894,13 @@ You can check if everything is correct by connecting to each server using
```
When connected to a `master` Redis, you will see the number of connected
`slaves`, and a list of each with connection details:
`replicas`, and a list of each with connection details:
```plaintext
# Replication
role:master
connected_slaves:1
slave0:ip=10.133.5.21,port=6379,state=online,offset=208037514,lag=1
connected_replicas:1
replica0:ip=10.133.5.21,port=6379,state=online,offset=208037514,lag=1
master_repl_offset:208037658
repl_backlog_active:1
repl_backlog_size:1048576
@ -908,21 +908,21 @@ repl_backlog_first_byte_offset:206989083
repl_backlog_histlen:1048576
```
When it's a `slave`, you will see details of the master connection and if
When it's a `replica`, you will see details of the master connection and if
its `up` or `down`:
```plaintext
# Replication
role:slave
role:replica
master_host:10.133.1.58
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:208096498
slave_priority:100
slave_read_only:1
connected_slaves:0
replica_repl_offset:208096498
replica_priority:100
replica_read_only:1
connected_replicas:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576

View File

@ -40,7 +40,7 @@ This is the section where we install and set up the new Redis instances.
- Since Redis 3.2, you must define a password to receive external connections
(`requirepass`).
- If you are using Redis with Sentinel, you will also need to define the same
password for the slave password definition (`masterauth`) in the same instance.
password for the replica password definition (`masterauth`) in the same instance.
In addition, read the prerequisites as described in the
[Omnibus Redis HA document](redis.md#prerequisites) since they provide some
@ -72,9 +72,9 @@ Assuming that the Redis master instance IP is `10.0.0.1`:
1. Restart the Redis service for the changes to take effect.
### Step 2. Configuring the slave Redis instances
### Step 2. Configuring the replica Redis instances
Assuming that the Redis slave instance IP is `10.0.0.2`:
Assuming that the Redis replica instance IP is `10.0.0.2`:
1. [Install Redis](../../install/installation.md#7-redis).
1. Edit `/etc/redis/redis.conf`:
@ -95,12 +95,12 @@ Assuming that the Redis slave instance IP is `10.0.0.2`:
requirepass redis-password-goes-here
masterauth redis-password-goes-here
## Define `slaveof` pointing to the Redis master instance with IP and port.
slaveof 10.0.0.1 6379
## Define `replicaof` pointing to the Redis master instance with IP and port.
replicaof 10.0.0.1 6379
```
1. Restart the Redis service for the changes to take effect.
1. Go through the steps again for all the other slave nodes.
1. Go through the steps again for all the other replica nodes.
### Step 3. Configuring the Redis Sentinel instances
@ -131,7 +131,7 @@ master with IP `10.0.0.1` (some settings might overlap with the master):
masterauth redis-password-goes-here
## Define with `sentinel auth-pass` the same shared password you have
## defined for both Redis master and slaves instances.
## defined for both Redis master and replicas instances.
sentinel auth-pass gitlab-redis redis-password-goes-here
## Define with `sentinel monitor` the IP and port of the Redis
@ -149,18 +149,18 @@ master with IP `10.0.0.1` (some settings might overlap with the master):
## already tried against the same master by a given Sentinel, is two
## times the failover timeout.
##
## * The time needed for a slave replicating to a wrong master according
## * The time needed for a replica replicating to a wrong master according
## to a Sentinel current configuration, to be forced to replicate
## with the right master, is exactly the failover timeout (counting since
## the moment a Sentinel detected the misconfiguration).
##
## * The time needed to cancel a failover that is already in progress but
## did not produced any configuration change (SLAVEOF NO ONE yet not
## acknowledged by the promoted slave).
## did not produced any configuration change (REPLICAOF NO ONE yet not
## acknowledged by the promoted replica).
##
## * The maximum time a failover in progress waits for all the slaves to be
## reconfigured as slaves of the new master. However even after this time
## the slaves will be reconfigured by the Sentinels anyway, but not with
## * The maximum time a failover in progress waits for all the replicas to be
## reconfigured as replicas of the new master. However even after this time
## the replicas will be reconfigured by the Sentinels anyway, but not with
## the exact parallel-syncs progression as specified.
sentinel failover_timeout 30000
```
@ -203,7 +203,7 @@ setup:
1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
## Example of minimal configuration with 1 master, 2 slaves and 3 Sentinels
## Example of minimal configuration with 1 master, 2 replicas and 3 Sentinels
In this example we consider that all servers have an internal network
interface with IPs in the `10.0.0.x` range, and that they can connect
@ -215,13 +215,13 @@ outside ([Internet](https://gitlab.com/gitlab-org/gitlab-foss/uploads/c4cc8cd353
For this example, **Sentinel 1** will be configured in the same machine as the
**Redis Master**, **Sentinel 2** and **Sentinel 3** in the same machines as the
**Slave 1** and **Slave 2** respectively.
**Replica 1** and **Replica 2** respectively.
Here is a list and description of each **machine** and the assigned **IP**:
- `10.0.0.1`: Redis Master + Sentinel 1
- `10.0.0.2`: Redis Slave 1 + Sentinel 2
- `10.0.0.3`: Redis Slave 2 + Sentinel 3
- `10.0.0.2`: Redis Replica 1 + Sentinel 2
- `10.0.0.3`: Redis Replica 2 + Sentinel 3
- `10.0.0.4`: GitLab application
Please note that after the initial configuration, if a failover is initiated
@ -257,7 +257,7 @@ or a failover promotes a different **Master** node.
1. Restart the Redis service for the changes to take effect.
### Example configuration for Redis slave 1 and Sentinel 2
### Example configuration for Redis replica 1 and Sentinel 2
1. In `/etc/redis/redis.conf`:
@ -266,7 +266,7 @@ or a failover promotes a different **Master** node.
port 6379
requirepass redis-password-goes-here
masterauth redis-password-goes-here
slaveof 10.0.0.1 6379
replicaof 10.0.0.1 6379
```
1. In `/etc/redis/sentinel.conf`:
@ -282,7 +282,7 @@ or a failover promotes a different **Master** node.
1. Restart the Redis service for the changes to take effect.
### Example configuration for Redis slave 2 and Sentinel 3
### Example configuration for Redis replica 2 and Sentinel 3
1. In `/etc/redis/redis.conf`:
@ -291,7 +291,7 @@ or a failover promotes a different **Master** node.
port 6379
requirepass redis-password-goes-here
masterauth redis-password-goes-here
slaveof 10.0.0.1 6379
replicaof 10.0.0.1 6379
```
1. In `/etc/redis/sentinel.conf`:

View File

@ -139,6 +139,7 @@ The following API resources are available outside of project and group contexts
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
| [Projects](projects.md) | `/users/:id/projects` (also available for projects) |
| [Project Repository Storage Moves](project_repository_storage_moves.md) | `/project_repository_storage_moves` |
| [Runners](runners.md) | `/runners` (also available for projects) |
| [Search](search.md) | `/search` (also available for groups and projects) |
| [Settings](settings.md) **(CORE ONLY)** | `/application/settings` |

View File

@ -1115,6 +1115,61 @@ type CreateImageDiffNotePayload {
note: Note
}
"""
Autogenerated input type of CreateIteration
"""
input CreateIterationInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The description of the iteration
"""
description: String
"""
The end date of the iteration
"""
dueDate: String
"""
The target group for the iteration
"""
groupPath: ID!
"""
The start date of the iteration
"""
startDate: String
"""
The title of the iteration
"""
title: String
}
"""
Autogenerated return type of CreateIteration
"""
type CreateIterationPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The created iteration
"""
iteration: Iteration
}
"""
Autogenerated input type of CreateNote
"""
@ -3186,6 +3241,11 @@ type EpicIssue implements Noteable {
"""
iid: ID!
"""
Iteration of the issue
"""
iteration: Iteration
"""
Labels of the issue
"""
@ -4075,6 +4135,53 @@ type Group {
updatedBefore: Time
): IssueConnection
"""
Find iterations
"""
iterations(
"""
Returns the elements in the list that come after the specified cursor.
"""
after: String
"""
Returns the elements in the list that come before the specified cursor.
"""
before: String
"""
List items within a time frame where items.end_date is between startDate and
endDate parameters (startDate parameter must be present)
"""
endDate: Time
"""
Returns the first _n_ elements from the list.
"""
first: Int
"""
Returns the last _n_ elements from the list.
"""
last: Int
"""
List items within a time frame where items.start_date is between startDate
and endDate parameters (endDate parameter must be present)
"""
startDate: Time
"""
Filter iterations by state
"""
state: IterationState
"""
Fuzzy search by title
"""
title: String
): IterationConnection
"""
Indicates if Large File Storage (LFS) is enabled for namespace
"""
@ -4536,6 +4643,11 @@ type Issue implements Noteable {
"""
iid: ID!
"""
Iteration of the issue
"""
iteration: Iteration
"""
Labels of the issue
"""
@ -4872,6 +4984,51 @@ type IssueSetDueDatePayload {
issue: Issue
}
"""
Autogenerated input type of IssueSetIteration
"""
input IssueSetIterationInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The iid of the issue to mutate
"""
iid: String!
"""
The iteration to assign to the issue.
"""
iterationId: ID
"""
The project the issue to mutate is in
"""
projectPath: ID!
}
"""
Autogenerated return type of IssueSetIteration
"""
type IssueSetIterationPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The issue after mutation
"""
issue: Issue
}
"""
Autogenerated input type of IssueSetWeight
"""
@ -5006,6 +5163,107 @@ enum IssueState {
opened
}
"""
Represents an iteration object.
"""
type Iteration {
"""
Timestamp of iteration creation
"""
createdAt: Time!
"""
Description of the iteration
"""
description: String
"""
Timestamp of the iteration due date
"""
dueDate: Time
"""
ID of the iteration
"""
id: ID!
"""
Timestamp of the iteration start date
"""
startDate: Time
"""
State of the iteration
"""
state: IterationState!
"""
Title of the iteration
"""
title: String!
"""
Timestamp of last iteration update
"""
updatedAt: Time!
"""
Web path of the iteration
"""
webPath: String!
"""
Web URL of the iteration
"""
webUrl: String!
}
"""
The connection type for Iteration.
"""
type IterationConnection {
"""
A list of edges.
"""
edges: [IterationEdge]
"""
A list of nodes.
"""
nodes: [Iteration]
"""
Information to aid in pagination.
"""
pageInfo: PageInfo!
}
"""
An edge in a connection.
"""
type IterationEdge {
"""
A cursor for use in pagination.
"""
cursor: String!
"""
The item at the end of the edge.
"""
node: Iteration
}
"""
State of a GitLab iteration
"""
enum IterationState {
all
closed
opened
started
upcoming
}
"""
Represents untyped JSON
"""
@ -6253,6 +6511,7 @@ type Mutation {
createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload
createEpic(input: CreateEpicInput!): CreateEpicPayload
createImageDiffNote(input: CreateImageDiffNoteInput!): CreateImageDiffNotePayload
createIteration(input: CreateIterationInput!): CreateIterationPayload
createNote(input: CreateNoteInput!): CreateNotePayload
createRequirement(input: CreateRequirementInput!): CreateRequirementPayload
createSnippet(input: CreateSnippetInput!): CreateSnippetPayload
@ -6266,6 +6525,7 @@ type Mutation {
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
issueSetConfidential(input: IssueSetConfidentialInput!): IssueSetConfidentialPayload
issueSetDueDate(input: IssueSetDueDateInput!): IssueSetDueDatePayload
issueSetIteration(input: IssueSetIterationInput!): IssueSetIterationPayload
issueSetWeight(input: IssueSetWeightInput!): IssueSetWeightPayload
jiraImportStart(input: JiraImportStartInput!): JiraImportStartPayload
markAsSpamSnippet(input: MarkAsSpamSnippetInput!): MarkAsSpamSnippetPayload
@ -8376,11 +8636,6 @@ type Release {
"""
descriptionHtml: String
"""
SHA of the release's evidence
"""
evidenceSha: String
"""
Milestones associated to the release
"""
@ -9246,7 +9501,6 @@ enum ServiceType {
HANGOUTS_CHAT_SERVICE
HIPCHAT_SERVICE
IRKER_SERVICE
JENKINS_DEPRECATED_SERVICE
JENKINS_SERVICE
JIRA_SERVICE
MATTERMOST_SERVICE

View File

@ -3016,6 +3016,148 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "CreateIterationInput",
"description": "Autogenerated input type of CreateIteration",
"fields": null,
"inputFields": [
{
"name": "groupPath",
"description": "The target group for the iteration",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "title",
"description": "The title of the iteration",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "description",
"description": "The description of the iteration",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "startDate",
"description": "The start date of the iteration",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "dueDate",
"description": "The end date of the iteration",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "CreateIterationPayload",
"description": "Autogenerated return type of CreateIteration",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "iteration",
"description": "The created iteration",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Iteration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "CreateNoteInput",
@ -9010,6 +9152,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "iteration",
"description": "Iteration of the issue",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Iteration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "labels",
"description": "Labels of the issue",
@ -11377,6 +11533,99 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "iterations",
"description": "Find iterations",
"args": [
{
"name": "startDate",
"description": "List items within a time frame where items.start_date is between startDate and endDate parameters (endDate parameter must be present)",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "endDate",
"description": "List items within a time frame where items.end_date is between startDate and endDate parameters (startDate parameter must be present)",
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"defaultValue": null
},
{
"name": "state",
"description": "Filter iterations by state",
"type": {
"kind": "ENUM",
"name": "IterationState",
"ofType": null
},
"defaultValue": null
},
{
"name": "title",
"description": "Fuzzy search by title",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "before",
"description": "Returns the elements in the list that come before the specified cursor.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "first",
"description": "Returns the first _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
},
{
"name": "last",
"description": "Returns the last _n_ elements from the list.",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "IterationConnection",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lfsEnabled",
"description": "Indicates if Large File Storage (LFS) is enabled for namespace",
@ -12608,6 +12857,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "iteration",
"description": "Iteration of the issue",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Iteration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "labels",
"description": "Labels of the issue",
@ -13611,6 +13874,132 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "IssueSetIterationInput",
"description": "Autogenerated input type of IssueSetIteration",
"fields": null,
"inputFields": [
{
"name": "projectPath",
"description": "The project the issue to mutate is in",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "iid",
"description": "The iid of the issue to mutate",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "iterationId",
"description": "The iteration to assign to the issue.\n",
"type": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "IssueSetIterationPayload",
"description": "Autogenerated return type of IssueSetIteration",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issue",
"description": "The issue after mutation",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "IssueSetWeightInput",
@ -13871,6 +14260,340 @@
],
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "Iteration",
"description": "Represents an iteration object.",
"fields": [
{
"name": "createdAt",
"description": "Timestamp of iteration creation",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": "Description of the iteration",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dueDate",
"description": "Timestamp of the iteration due date",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "id",
"description": "ID of the iteration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "startDate",
"description": "Timestamp of the iteration start date",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "state",
"description": "State of the iteration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "IterationState",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "title",
"description": "Title of the iteration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "updatedAt",
"description": "Timestamp of last iteration update",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Time",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "webPath",
"description": "Web path of the iteration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "webUrl",
"description": "Web URL of the iteration",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "IterationConnection",
"description": "The connection type for Iteration.",
"fields": [
{
"name": "edges",
"description": "A list of edges.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "IterationEdge",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "nodes",
"description": "A list of nodes.",
"args": [
],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Iteration",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pageInfo",
"description": "Information to aid in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "PageInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "IterationEdge",
"description": "An edge in a connection.",
"fields": [
{
"name": "cursor",
"description": "A cursor for use in pagination.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "node",
"description": "The item at the end of the edge.",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Iteration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "IterationState",
"description": "State of a GitLab iteration",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "upcoming",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "started",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "opened",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "closed",
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "all",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "JSON",
@ -17779,6 +18502,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createIteration",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "CreateIterationInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "CreateIterationPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "createNote",
"description": null,
@ -18130,6 +18880,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issueSetIteration",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "IssueSetIterationInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "IssueSetIterationPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issueSetWeight",
"description": null,
@ -24597,20 +25374,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "evidenceSha",
"description": "SHA of the release's evidence",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "milestones",
"description": "Milestones associated to the release",
@ -27478,12 +28241,6 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "JENKINS_DEPRECATED_SERVICE",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null

View File

@ -204,6 +204,16 @@ Autogenerated return type of CreateImageDiffNote
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `note` | Note | The note after mutation |
## CreateIterationPayload
Autogenerated return type of CreateIteration
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `iteration` | Iteration | The created iteration |
## CreateNotePayload
Autogenerated return type of CreateNote
@ -521,6 +531,7 @@ Relationship between an epic and an issue
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
| `id` | ID | Global ID of the epic-issue relation |
| `iid` | ID! | Internal ID of the issue |
| `iteration` | Iteration | Iteration of the issue |
| `milestone` | Milestone | Milestone of the issue |
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
| `relationPath` | String | URI path of the epic-issue relation |
@ -660,6 +671,7 @@ Autogenerated return type of EpicTreeReorder
| `epic` | Epic | Epic to which this issue belongs |
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
| `iid` | ID! | Internal ID of the issue |
| `iteration` | Iteration | Iteration of the issue |
| `milestone` | Milestone | Milestone of the issue |
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
@ -713,6 +725,16 @@ Autogenerated return type of IssueSetDueDate
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
## IssueSetIterationPayload
Autogenerated return type of IssueSetIteration
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
## IssueSetWeightPayload
Autogenerated return type of IssueSetWeight
@ -723,6 +745,23 @@ Autogenerated return type of IssueSetWeight
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
## Iteration
Represents an iteration object.
| Name | Type | Description |
| --- | ---- | ---------- |
| `createdAt` | Time! | Timestamp of iteration creation |
| `description` | String | Description of the iteration |
| `dueDate` | Time | Timestamp of the iteration due date |
| `id` | ID! | ID of the iteration |
| `startDate` | Time | Timestamp of the iteration start date |
| `state` | IterationState! | State of the iteration |
| `title` | String! | Title of the iteration |
| `updatedAt` | Time! | Timestamp of last iteration update |
| `webPath` | String! | Web path of the iteration |
| `webUrl` | String! | Web URL of the iteration |
## JiraImport
| Name | Type | Description |
@ -1166,7 +1205,6 @@ Information about pagination in a connection.
| `createdAt` | Time | Timestamp of when the release was created |
| `description` | String | Description (also known as "release notes") of the release |
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `evidenceSha` | String | SHA of the release's evidence |
| `name` | String | Name of the release |
| `releasedAt` | Time | Timestamp of when the release was released |
| `tagName` | String! | Name of the tag associated with the release |

View File

@ -0,0 +1,80 @@
# Project repository storage move API
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/31285) in GitLab 13.0.
Project repository storage can be moved. To retrieve project repository storage moves using the API, you must [authenticate yourself](README.md#authentication) as an administrator.
## Retrieve all project repository storage moves
```text
GET /project_repository_storage_moves
```
By default, `GET` requests return 20 results at a time because the API results
are [paginated](README.md#pagination).
Example request:
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://primary.example.com/api/v4/project_repository_storage_moves'
```
Example response:
```json
[
{
"id": 1,
"created_at": "2020-05-07T04:27:17.234Z",
"state": "scheduled",
"source_storage_name": "default",
"destination_storage_name": "storage2",
"project": {
"id": 1,
"description": null,
"name": "project1",
"name_with_namespace": "John Doe2 / project1",
"path": "project1",
"path_with_namespace": "namespace1/project1",
"created_at": "2020-05-07T04:27:17.016Z"
}
]
```
## Get a single project repository storage move
```text
GET /project_repository_storage_moves/:id
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project repository storage move |
Example request:
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" 'https://primary.example.com/api/v4/project_repository_storage_moves/1'
```
Example response:
```json
{
"id": 1,
"created_at": "2020-05-07T04:27:17.234Z",
"state": "scheduled",
"source_storage_name": "default",
"destination_storage_name": "storage2",
"project": {
"id": 1,
"description": null,
"name": "project1",
"name_with_namespace": "John Doe2 / project1",
"path": "project1",
"path_with_namespace": "namespace1/project1",
"created_at": "2020-05-07T04:27:17.016Z"
}
```

View File

@ -1303,6 +1303,9 @@ GET /projects/:id/services/jenkins
A continuous integration and build server
NOTE: **Note:**
This service was [removed in v13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/1600)
### Create/Edit Jenkins CI (Deprecated) service
Set Jenkins CI (Deprecated) service for a project.

View File

@ -214,7 +214,7 @@ Redis process.
Single-core: Like PgBouncer, a single Redis process can only use one
core. It does not support multi-threading.
Dumb secondaries: Redis secondaries (aka slaves) don't actually
Dumb secondaries: Redis secondaries (aka replicas) don't actually
handle any load. Unlike PostgreSQL secondaries, they don't even serve
read queries. They simply replicate data from the primary and take over
only when the primary fails.
@ -231,8 +231,8 @@ election to determine a new leader.
No leader: A Redis cluster can get into a mode where there are no
primaries. For example, this can happen if Redis nodes are misconfigured
to follow the wrong node. Sometimes this requires forcing one node to
become a primary via the [`SLAVEOF NO ONE`
command](https://redis.io/commands/slaveof).
become a primary via the [`REPLICAOF NO ONE`
command](https://redis.io/commands/replicaof).
### Sidekiq

View File

@ -48,15 +48,18 @@ to avoid getting this error, you need to remove all instances of the
**Omnibus Installation**
```shell
sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService', 'GithubService']).delete_all"
sudo gitlab-rails runner "Service.where(type: ['JenkinsService', 'GithubService']).delete_all"
```
**Source Installation**
```shell
bundle exec rails runner "Service.where(type: ['JenkinsService', 'JenkinsDeprecatedService', 'GithubService']).delete_all" production
bundle exec rails runner "Service.where(type: ['JenkinsService', 'GithubService']).delete_all" production
```
NOTE: **Note:**
If you are running `GitLab =< v13.0` you need to also remove `JenkinsDeprecatedService` records.
### Variables environment scopes
If you're using this feature and there are variables sharing the same

View File

@ -79,7 +79,7 @@ As we'll be using [Amazon S3 object storage](#amazon-s3-object-storage), our EC2
"Effect": "Allow",
"Action": [
"s3:AbortMultipartUpload",
"s3::CompleteMultipartUpload",
"s3:CompleteMultipartUpload",
"s3:ListBucket",
"s3:PutObject",
"s3:GetObject",

View File

@ -152,8 +152,8 @@ The following Elasticsearch settings are available:
| `AWS Access Key` | The AWS access key. |
| `AWS Secret Access Key` | The AWS secret access key. |
| `Maximum field length` | See [the explanation in instance limits.](../administration/instance_limits.md#maximum-field-length). |
| `Maximum bulk request size (MiB)` | Repository indexing uses the Elasticsearch bulk request API. This setting determines the maximum size of an individual bulk request during these operations. |
| `Bulk request concurrency` | Each repository indexing operation may submit bulk requests in parallel. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. |
| `Maximum bulk request size (MiB)` | The Maximum Bulk Request size is used by GitLab's Golang-based indexer processes and indicates how much data it ought to collect (and store in memory) in a given indexing process before submitting the payload to Elasticsearchs Bulk API. This setting works in tandem with the Bulk request concurrency setting (see below) and needs to accomodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
| `Bulk request concurrency` | The Bulk request concurrency indicates how many of GitLab's Golang-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to Elasticsearchs Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting works in tandem with the Maximum bulk request size setting (see above) and needs to accomodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
### Limiting namespaces and projects

View File

@ -6,6 +6,9 @@ was deprecated in favor of the
[GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin).
Please use documentation for the new [Jenkins CI service](jenkins.md).
NOTE: **Note:**
This service was [removed in v13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/1600)
Integration includes:
- Trigger Jenkins build after push to repo

View File

@ -257,7 +257,6 @@ but commented out to help encourage others to add to it in the future. -->
|projects_hipchat_active|counts||
|projects_irker_active|counts||
|projects_jenkins_active|counts||
|projects_jenkins_deprecated_active|counts||
|projects_jira_active -|counts||
|projects_mattermost_active|counts||
|projects_mattermost_slash_commands_active|counts||
@ -313,7 +312,9 @@ but commented out to help encourage others to add to it in the future. -->
|epics|counts||
|feature_flags|counts||
|geo_nodes|counts||
|incident_issues|counts||
|incident_issues|counts|monitor|Issues created by the alert bot|
|alert_bot_incident_issues|counts|monitor|Issues created by the alert bot|
|incident_labeled_issues|counts|monitor|Issues with the incident label|
|ldap_group_links|counts||
|ldap_keys|counts||
|ldap_users|counts||

View File

@ -68,7 +68,9 @@ You should then be able to see the **Packages & Registries** section on the left
You will need the following:
- Your GitLab username.
- A personal access token. You can generate a [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api` for repository authentication.
- A personal access token or deploy token. For repository authentication:
- You can generate a [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`.
- You can generate a [deploy token](./../../project/deploy_tokens/index.md) with the scope set to `read_package_registry`, `write_package_registry`, or both.
- A suitable name for your source.
- Your project ID which can be found on the home page of your project.
@ -83,7 +85,7 @@ You can now add a new source to NuGet with:
To add the GitLab NuGet Repository as a source with `nuget`:
```shell
nuget source Add -Name <source_name> -Source "https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username> -Password <gitlab_personal_access_token>
nuget source Add -Name <source_name> -Source "https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username or deploy_token_username> -Password <gitlab_personal_access_token or deploy_token>
```
Where:
@ -107,8 +109,8 @@ nuget source Add -Name "GitLab" -Source "https//gitlab.example/api/v4/projects/1
- **Location**: `https://gitlab.com/api/v4/projects/<your_project_id>/packages/nuget/index.json`
- Replace `<your_project_id>` with your project ID.
- If you have a self-managed GitLab installation, replace `gitlab.com` with your domain name.
- **Username**: Your GitLab username
- **Password**: Your personal access token
- **Username**: Your GitLab username or deploy token username
- **Password**: Your personal access token or deploy token
![Visual Studio Adding a NuGet source](img/visual_studio_adding_nuget_source.png)
@ -131,8 +133,8 @@ To add the GitLab NuGet Repository as a source for .NET, create a file named `nu
</packageSources>
<packageSourceCredentials>
<gitlab>
<add key="Username" value="<gitlab_username>" />
<add key="ClearTextPassword" value="<gitlab_personal_access_token>" />
<add key="Username" value="<gitlab_username or deploy_token_username>" />
<add key="ClearTextPassword" value="<gitlab_personal_access_token or deploy_token>" />
</gitlab>
</packageSourceCredentials>
</configuration>

View File

@ -150,6 +150,8 @@ Package Registry**. Before we do so, we next need to set up authentication.
## Adding the GitLab PyPi Repository as a source
### Authenticating with a personal access token
You will need the following:
- A personal access token. You can generate a [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api` for repository authentication.
@ -169,6 +171,27 @@ username = __token__
password = <your personal access token>
```
### Authenticating with a deploy token
You will need the following:
- A deploy token. You can generate a [deploy token](./../../project/deploy_tokens/index.md) with the `read_package_registry` and/or `write_package_registry` scopes for repository authentication.
- A suitable name for your source.
- Your project ID which can be found on the home page of your project.
Edit your `~/.pypirc` file and add the following:
```ini
[distutils]
index-servers =
gitlab
[gitlab]
repository = https://gitlab.com/api/v4/projects/<project_id>/packages/pypi
username = <deploy token username>
password = <deploy token>
```
## Uploading packages
When uploading packages, note that:

View File

@ -180,6 +180,7 @@ module API
mount ::API::ProjectImport
mount ::API::ProjectHooks
mount ::API::ProjectMilestones
mount ::API::ProjectRepositoryStorageMoves
mount ::API::Projects
mount ::API::ProjectSnapshots
mount ::API::ProjectSnippets

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module API
module Entities
class ProjectRepositoryStorageMove < Grape::Entity
expose :id
expose :created_at
expose :human_state_name, as: :state
expose :source_storage_name
expose :destination_storage_name
expose :project, using: Entities::ProjectIdentity
end
end
end

View File

@ -21,7 +21,6 @@ module API
expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? }
expose :commit_path, expose_nil: false
expose :tag_path, expose_nil: false
expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? }
expose :assets do
expose :assets_count, as: :count do |release, _|
@ -32,7 +31,6 @@ module API
expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted
end
expose :evidence_file_path, expose_nil: false, if: ->(_, _) { can_download_code? }
end
expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_download_code? }
expose :_links do

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
module API
class ProjectRepositoryStorageMoves < Grape::API
include PaginationParams
before { authenticated_as_admin! }
resource :project_repository_storage_moves do
desc 'Get a list of all project repository storage moves' do
detail 'This feature was introduced in GitLab 13.0.'
success Entities::ProjectRepositoryStorageMove
end
params do
use :pagination
end
get do
storage_moves = ProjectRepositoryStorageMove.with_projects.order_created_at_desc
present paginate(storage_moves), with: Entities::ProjectRepositoryStorageMove, current_user: current_user
end
desc 'Get a project repository storage move' do
detail 'This feature was introduced in GitLab 13.0.'
success Entities::ProjectRepositoryStorageMove
end
get ':id' do
storage_move = ProjectRepositoryStorageMove.find(params[:id])
present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user
end
end
end
end

View File

@ -104,11 +104,17 @@ module Gitlab
# This returns a deploy token, not a user since a deploy token does not
# belong to a user.
#
# deploy tokens are accepted with deploy token headers and basic auth headers
def deploy_token_from_request
return unless route_authentication_setting[:deploy_token_allowed]
token = current_request.env[DEPLOY_TOKEN_HEADER].presence || parsed_oauth_token
if has_basic_credentials?(current_request)
_, token = user_name_and_password(current_request)
end
deploy_token = DeployToken.active.find_by_token(token)
@current_authenticated_deploy_token = deploy_token # rubocop:disable Gitlab/ModuleWithInstanceVariables

View File

@ -17,7 +17,7 @@ license_management:
refs:
- branches
variables:
- $GITLAB_FEATURES =~ /\blicense_management\b/
- $GITLAB_FEATURES =~ /\blicense_scanning\b/
except:
refs:
- master

View File

@ -40,6 +40,8 @@ module Gitlab
end
if order_list.count > 2
# Keep in mind an order clause for primary key is added if one is not present
# lib/gitlab/graphql/pagination/keyset/connection.rb:97
raise ArgumentError.new('A maximum of 2 ordering fields are allowed')
end

View File

@ -63,6 +63,8 @@ module Gitlab
# rubocop: disable Metrics/AbcSize
# rubocop: disable CodeReuse/ActiveRecord
def system_usage_data
alert_bot_incident_count = count(::Issue.authored(::User.alert_bot))
{
counts: {
assignee_lists: count(List.assignee),
@ -112,7 +114,9 @@ module Gitlab
issues_with_associated_zoom_link: count(ZoomMeeting.added_to_issue),
issues_using_zoom_quick_actions: distinct_count(ZoomMeeting, :issue_id),
issues_with_embedded_grafana_charts_approx: grafana_embed_usage_data,
incident_issues: count(::Issue.authored(::User.alert_bot)),
incident_issues: alert_bot_incident_count,
alert_bot_incident_issues: alert_bot_incident_count,
incident_labeled_issues: count(::Issue.with_label_attributes(IncidentManagement::CreateIssueService::INCIDENT_LABEL)),
keys: count(Key),
label_lists: count(List.label),
lfs_objects: count(LfsObject),

View File

@ -1120,7 +1120,7 @@ msgstr ""
msgid "Active"
msgstr ""
msgid "Active %{type} Tokens (%{token_length})"
msgid "Active %{type} (%{token_length})"
msgstr ""
msgid "Active Sessions"
@ -1173,7 +1173,7 @@ msgstr ""
msgid "Add Zoom meeting"
msgstr ""
msgid "Add a %{type} token"
msgid "Add a %{type}"
msgstr ""
msgid "Add a GPG key"
@ -1224,6 +1224,9 @@ msgstr ""
msgid "Add an existing issue"
msgstr ""
msgid "Add an impersonation token"
msgstr ""
msgid "Add an issue"
msgstr ""
@ -2659,7 +2662,7 @@ msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
msgid "Are you sure you want to revoke this %{type} Token? This action cannot be undone."
msgid "Are you sure you want to revoke this %{type}? This action cannot be undone."
msgstr ""
msgid "Are you sure you want to revoke this nickname?"
@ -4358,6 +4361,9 @@ msgstr ""
msgid "Close"
msgstr ""
msgid "Close %{display_issuable_type}"
msgstr ""
msgid "Close %{tabname}"
msgstr ""
@ -6042,6 +6048,9 @@ msgstr ""
msgid "Copy %{proxy_url}"
msgstr ""
msgid "Copy %{type}"
msgstr ""
msgid "Copy Account ID to clipboard"
msgstr ""
@ -6087,9 +6096,6 @@ msgstr ""
msgid "Copy file path"
msgstr ""
msgid "Copy impersonation token"
msgstr ""
msgid "Copy key"
msgstr ""
@ -6105,9 +6111,6 @@ msgstr ""
msgid "Copy link to chart"
msgstr ""
msgid "Copy personal access token"
msgstr ""
msgid "Copy reference"
msgstr ""
@ -6204,7 +6207,7 @@ msgstr ""
msgid "Create %{environment}"
msgstr ""
msgid "Create %{type} token"
msgid "Create %{type}"
msgstr ""
msgid "Create New Directory"
@ -8147,6 +8150,9 @@ msgstr ""
msgid "Enter the merge request title"
msgstr ""
msgid "Enter the name of your application, and we'll return a unique %{type}."
msgstr ""
msgid "Enter your password to approve"
msgstr ""
@ -11337,6 +11343,9 @@ msgstr ""
msgid "ImageViewerDimensions|W"
msgstr ""
msgid "Impersonation Tokens"
msgstr ""
msgid "Impersonation has been disabled"
msgstr ""
@ -11954,6 +11963,18 @@ msgstr ""
msgid "It's you"
msgstr ""
msgid "Iterations"
msgstr ""
msgid "Iteration|Dates cannot overlap with other existing Iterations"
msgstr ""
msgid "Iteration|cannot be in the past"
msgstr ""
msgid "Iteration|cannot be more than 500 years in the future"
msgstr ""
msgid "Jaeger URL"
msgstr ""
@ -13904,6 +13925,9 @@ msgid_plural "New Issues"
msgstr[0] ""
msgstr[1] ""
msgid "New Iteration"
msgstr ""
msgid "New Jira import"
msgstr ""
@ -15181,9 +15205,6 @@ msgstr ""
msgid "Pick a name"
msgstr ""
msgid "Pick a name for the application, and we'll give you a unique %{type} token."
msgstr ""
msgid "Pin code"
msgstr ""
@ -15982,9 +16003,6 @@ msgstr ""
msgid "Profiles|Give your individual key a title"
msgstr ""
msgid "Profiles|Impersonation"
msgstr ""
msgid "Profiles|Include private contributions on my profile"
msgstr ""
@ -16030,9 +16048,6 @@ msgstr ""
msgid "Profiles|Path"
msgstr ""
msgid "Profiles|Personal Access"
msgstr ""
msgid "Profiles|Position and size your new avatar"
msgstr ""
@ -16177,12 +16192,6 @@ msgstr ""
msgid "Profiles|e.g. My MacBook key"
msgstr ""
msgid "Profiles|impersonation"
msgstr ""
msgid "Profiles|personal access"
msgstr ""
msgid "Profiles|username"
msgstr ""
@ -16234,6 +16243,9 @@ msgstr ""
msgid "Project '%{project_name}' will be deleted on %{date}"
msgstr ""
msgid "Project Access Tokens"
msgstr ""
msgid "Project Audit Events"
msgstr ""
@ -17711,6 +17723,9 @@ msgstr ""
msgid "Reopen"
msgstr ""
msgid "Reopen %{display_issuable_type}"
msgstr ""
msgid "Reopen epic"
msgstr ""
@ -17759,9 +17774,15 @@ msgstr ""
msgid "Repo by URL"
msgstr ""
msgid "Report %{display_issuable_type} that are abusive, inappropriate or spam."
msgstr ""
msgid "Report Type: %{report_type}"
msgstr ""
msgid "Report abuse"
msgstr ""
msgid "Report abuse to admin"
msgstr ""
@ -21938,6 +21959,9 @@ msgstr ""
msgid "This project does not have billing enabled. To create a cluster, <a href=%{linkToBilling} target=\"_blank\" rel=\"noopener noreferrer\">enable billing <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i></a> and try again."
msgstr ""
msgid "This project has no active access tokens."
msgstr ""
msgid "This project is archived and cannot be commented on."
msgstr ""
@ -21983,7 +22007,7 @@ msgstr ""
msgid "This user cannot be unlocked manually from GitLab"
msgstr ""
msgid "This user has no active %{type} Tokens."
msgid "This user has no active %{type}."
msgstr ""
msgid "This user has no identities"
@ -22488,6 +22512,9 @@ msgstr ""
msgid "Toggle commit list"
msgstr ""
msgid "Toggle dropdown"
msgstr ""
msgid "Toggle emoji award"
msgstr ""
@ -24447,6 +24474,9 @@ msgstr ""
msgid "You can also upload existing files from your computer using the instructions below."
msgstr ""
msgid "You can also use project access tokens to authenticate against Git over HTTP."
msgstr ""
msgid "You can always edit this later"
msgstr ""
@ -24474,6 +24504,9 @@ msgstr ""
msgid "You can filter by 'days to merge' by clicking on the columns in the chart."
msgstr ""
msgid "You can generate an access token scoped to this project for each application to use the GitLab API."
msgstr ""
msgid "You can get started by cloning the repository or start adding files to it with one of the following options."
msgstr ""
@ -24753,6 +24786,9 @@ msgstr ""
msgid "You will receive notifications only for comments in which you were @mentioned"
msgstr ""
msgid "You won't be able to create new projects because you have reached your project limit."
msgstr ""
msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account"
msgstr ""
@ -24846,9 +24882,6 @@ msgstr ""
msgid "Your Groups"
msgstr ""
msgid "Your New Personal Access Token"
msgstr ""
msgid "Your Personal Access Tokens will expire in %{days_to_expire} days or less"
msgstr ""
@ -24957,6 +24990,9 @@ msgstr ""
msgid "Your name"
msgstr ""
msgid "Your new %{type}"
msgstr ""
msgid "Your new SCIM token"
msgstr ""
@ -25514,6 +25550,12 @@ msgstr ""
msgid "image diff"
msgstr ""
msgid "impersonation token"
msgstr ""
msgid "impersonation tokens"
msgstr ""
msgid "import flow"
msgstr ""
@ -26053,6 +26095,12 @@ msgstr ""
msgid "per day"
msgstr ""
msgid "personal access token"
msgstr ""
msgid "personal access tokens"
msgstr ""
msgid "pipeline"
msgstr ""
@ -26081,6 +26129,12 @@ msgid_plural "projects"
msgstr[0] ""
msgstr[1] ""
msgid "project access token"
msgstr ""
msgid "project access tokens"
msgstr ""
msgid "project avatar"
msgstr ""

View File

@ -6,9 +6,9 @@ module QA
module Page
module Profile
class PersonalAccessTokens < Page::Base
view 'app/views/shared/_personal_access_tokens_form.html.haml' do
view 'app/views/shared/access_tokens/_form.html.haml' do
element :expiry_date_field
element :personal_access_token_name_field
element :access_token_name_field
element :create_token_button
end
@ -16,15 +16,15 @@ module QA
element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
view 'app/views/shared/_personal_access_tokens_created_container.html.haml' do
element :created_personal_access_token
view 'app/views/shared/access_tokens/_created_container.html.haml' do
element :created_access_token
end
view 'app/views/shared/_personal_access_tokens_table.html.haml' do
view 'app/views/shared/access_tokens/_table.html.haml' do
element :revoke_button
end
def fill_token_name(name)
fill_element(:personal_access_token_name_field, name)
fill_element(:access_token_name_field, name)
end
def check_api
@ -36,7 +36,7 @@ module QA
end
def created_access_token
find_element(:created_personal_access_token, wait: 30).value
find_element(:created_access_token, wait: 30).value
end
def fill_expiry_date(date)

View File

@ -1,8 +1,14 @@
# frozen_string_literal: true
FactoryBot.define do
sequence(:sequential_date) do |n|
n.days.from_now
end
factory :iteration do
title
start_date { generate(:sequential_date) }
due_date { generate(:sequential_date) }
transient do
project { nil }
@ -12,17 +18,22 @@ FactoryBot.define do
resource_parent { nil }
end
trait :active do
state { Iteration::STATE_ID_MAP[:active] }
trait :upcoming do
state_enum { Iteration::STATE_ENUM_MAP[:upcoming] }
end
trait :started do
state_enum { Iteration::STATE_ENUM_MAP[:started] }
end
trait :closed do
state { Iteration::STATE_ID_MAP[:closed] }
state_enum { Iteration::STATE_ENUM_MAP[:closed] }
end
trait :with_dates do
start_date { Date.new(2000, 1, 1) }
due_date { Date.new(2000, 1, 30) }
trait(:skip_future_date_validation) do
after(:stub, :build) do |iteration|
iteration.skip_future_date_validation = true
end
end
after(:build, :stub) do |iteration, evaluator|
@ -42,7 +53,8 @@ FactoryBot.define do
end
end
factory :active_iteration, traits: [:active]
factory :upcoming_iteration, traits: [:upcoming]
factory :started_iteration, traits: [:started]
factory :closed_iteration, traits: [:closed]
end
end

View File

@ -40,6 +40,16 @@ FactoryBot.define do
create_list(:zoom_meeting, 2, project: projects[0], issue: projects[0].issues[2], issue_status: :removed)
create(:sentry_issue, issue: projects[0].issues[0])
# Incident Labeled Issues
incident_label_attrs = IncidentManagement::CreateIssueService::INCIDENT_LABEL
incident_label = create(:label, project: projects[0], **incident_label_attrs)
create(:labeled_issue, project: projects[0], labels: [incident_label])
incident_group = create(:group)
incident_label_scoped_to_project = create(:label, project: projects[1], **incident_label_attrs)
incident_label_scoped_to_group = create(:group_label, group: incident_group, **incident_label_attrs)
create(:labeled_issue, project: projects[1], labels: [incident_label_scoped_to_project])
create(:labeled_issue, project: projects[1], labels: [incident_label_scoped_to_group])
# Enabled clusters
gcp_cluster = create(:cluster_provider_gcp, :created).cluster
create(:cluster_provider_aws, :created)

View File

@ -70,7 +70,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do
accept_confirm { click_on "Revoke" }
expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.")
expect(no_personal_access_tokens_message).to have_text("This user has no active impersonation tokens.")
end
it "removes expired tokens from 'active' section" do
@ -79,7 +79,7 @@ describe 'Admin > Users > Impersonation Tokens', :js do
visit admin_user_impersonation_tokens_path(user_id: user.username)
expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Impersonation Tokens.")
expect(no_personal_access_tokens_message).to have_text("This user has no active impersonation tokens.")
end
end
end

View File

@ -86,7 +86,7 @@ describe 'Profile > Personal Access Tokens', :js do
accept_confirm { click_on "Revoke" }
expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.")
expect(no_personal_access_tokens_message).to have_text("This user has no active personal access tokens.")
end
it "removes expired tokens from 'active' section" do
@ -94,7 +94,7 @@ describe 'Profile > Personal Access Tokens', :js do
visit profile_personal_access_tokens_path
expect(page).to have_selector(".settings-message")
expect(no_personal_access_tokens_message).to have_text("This user has no active Personal Access Tokens.")
expect(no_personal_access_tokens_message).to have_text("This user has no active personal access tokens.")
end
context "when revocation fails" do

View File

@ -34,7 +34,7 @@ describe 'Project members list' do
expect(second_row).to be_blank
end
it 'update user acess level', :js do
it 'update user access level', :js do
project.add_developer(user2)
visit_members_page
@ -86,6 +86,23 @@ describe 'Project members list' do
end
end
context 'project bots' do
let(:project_bot) { create(:user, :project_bot, name: 'project_bot') }
before do
project.add_maintainer(project_bot)
end
it 'does not show form used to change roles and "Expiration date" or the remove user button' do
project_member = project.project_members.find_by(user_id: project_bot.id)
visit_members_page
expect(page).not_to have_selector("#edit_project_member_#{project_member.id}")
expect(page).not_to have_selector("#project_member_#{project_member.id} .btn-remove")
end
end
def add_user(id, role)
page.within ".invite-users-form" do
select2(id, from: "#user_ids", multiple: true)

View File

@ -0,0 +1,93 @@
# frozen_string_literal: true
require 'spec_helper'
describe 'Project > Settings > Access Tokens', :js do
let_it_be(:user) { create(:user) }
let_it_be(:bot_user) { create(:user, :project_bot) }
let_it_be(:project) { create(:project) }
before_all do
project.add_maintainer(user)
end
before do
sign_in(user)
end
def create_project_access_token
project.add_maintainer(bot_user)
create(:personal_access_token, user: bot_user)
end
def active_project_access_tokens
find('.table.active-tokens')
end
def no_project_access_tokens_message
find('.settings-message')
end
def created_project_access_token
find('#created-personal-access-token').value
end
describe 'token creation' do
it 'allows creation of a project access token' do
name = 'My project access token'
visit project_settings_access_tokens_path(project)
fill_in 'Name', with: name
# Set date to 1st of next month
find_field('Expires at').click
find('.pika-next').click
click_on '1'
# Scopes
check 'api'
check 'read_api'
click_on 'Create project access token'
expect(active_project_access_tokens).to have_text(name)
expect(active_project_access_tokens).to have_text('In')
expect(active_project_access_tokens).to have_text('api')
expect(active_project_access_tokens).to have_text('read_api')
expect(created_project_access_token).not_to be_empty
end
end
describe 'active tokens' do
let!(:project_access_token) { create_project_access_token }
it 'shows active project access tokens' do
visit project_settings_access_tokens_path(project)
expect(active_project_access_tokens).to have_text(project_access_token.name)
end
end
describe 'inactive tokens' do
let!(:project_access_token) { create_project_access_token }
no_active_tokens_text = 'This project has no active access tokens.'
it 'allows revocation of an active token' do
visit project_settings_access_tokens_path(project)
accept_confirm { click_on 'Revoke' }
expect(page).to have_selector('.settings-message')
expect(no_project_access_tokens_message).to have_text(no_active_tokens_text)
end
it 'removes expired tokens from active section' do
project_access_token.update(expires_at: 5.days.ago)
visit project_settings_access_tokens_path(project)
expect(page).to have_selector('.settings-message')
expect(no_project_access_tokens_message).to have_text(no_active_tokens_text)
end
end
end

View File

@ -0,0 +1,20 @@
{
"type": "object",
"required": [
"id",
"created_at",
"state",
"source_storage_name",
"destination_storage_name",
"project"
],
"properties" : {
"id": { "type": "integer" },
"created_at": { "type": "date" },
"state": { "type": "string" },
"source_storage_name": { "type": "string" },
"destination_storage_name": { "type": "string" },
"project": { "type": "object" }
},
"additionalProperties": false
}

View File

@ -0,0 +1,6 @@
{
"type": "array",
"items": {
"$ref": "./project_repository_storage_move.json"
}
}

View File

@ -7,7 +7,7 @@ development:
host: localhost
port: 26380 # point to sentinel, not to redis port
-
host: slave2
host: replica2
port: 26380 # point to sentinel, not to redis port
test:
url: redis://:mynewpassword@localhost:6380/10
@ -16,14 +16,14 @@ test:
host: localhost
port: 26380 # point to sentinel, not to redis port
-
host: slave2
host: replica2
port: 26380 # point to sentinel, not to redis port
production:
url: redis://:mynewpassword@localhost:6380/10
sentinels:
-
host: slave1
host: replica1
port: 26380 # point to sentinel, not to redis port
-
host: slave2
host: replica2
port: 26380 # point to sentinel, not to redis port

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