Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c68905777e
commit
be1b7b709e
|
|
@ -40,7 +40,7 @@ export function formatListIssues(listIssues) {
|
|||
let listItemsCount;
|
||||
|
||||
const listData = listIssues.nodes.reduce((map, list) => {
|
||||
listItemsCount = list.issues.count;
|
||||
listItemsCount = list.issuesCount;
|
||||
let sortedIssues = list.issues.edges.map((issueNode) => ({
|
||||
...issueNode.node,
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
<script>
|
||||
import {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownText,
|
||||
GlSearchBoxByType,
|
||||
GlDropdownDivider,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
||||
import { __, s__ } from '~/locale';
|
||||
import projectMilestones from '../../graphql/project_milestones.query.graphql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BoardEditableItem,
|
||||
GlDropdown,
|
||||
GlLoadingIcon,
|
||||
GlDropdownItem,
|
||||
GlDropdownText,
|
||||
GlSearchBoxByType,
|
||||
GlDropdownDivider,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
milestones: [],
|
||||
searchTitle: '',
|
||||
loading: false,
|
||||
edit: false,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
milestones: {
|
||||
query: projectMilestones,
|
||||
debounce: 250,
|
||||
skip() {
|
||||
return !this.edit;
|
||||
},
|
||||
variables() {
|
||||
return {
|
||||
fullPath: this.projectPath,
|
||||
searchTitle: this.searchTitle,
|
||||
state: 'active',
|
||||
includeAncestors: true,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
const edges = data?.project?.milestones?.edges ?? [];
|
||||
return edges.map((item) => item.node);
|
||||
},
|
||||
error(error) {
|
||||
this.setError({ error, message: this.$options.i18n.fetchMilestonesError });
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['activeBoardItem']),
|
||||
hasMilestone() {
|
||||
return this.activeBoardItem.milestone !== null;
|
||||
},
|
||||
groupFullPath() {
|
||||
const { referencePath = '' } = this.activeBoardItem;
|
||||
return referencePath.slice(0, referencePath.indexOf('/'));
|
||||
},
|
||||
projectPath() {
|
||||
const { referencePath = '' } = this.activeBoardItem;
|
||||
return referencePath.slice(0, referencePath.indexOf('#'));
|
||||
},
|
||||
dropdownText() {
|
||||
return this.activeBoardItem.milestone?.title ?? this.$options.i18n.noMilestone;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setActiveIssueMilestone', 'setError']),
|
||||
handleOpen() {
|
||||
this.edit = true;
|
||||
this.$refs.dropdown.show();
|
||||
},
|
||||
handleClose() {
|
||||
this.edit = false;
|
||||
this.$refs.sidebarItem.collapse();
|
||||
},
|
||||
async setMilestone(milestoneId) {
|
||||
this.loading = true;
|
||||
this.searchTitle = '';
|
||||
this.handleClose();
|
||||
|
||||
try {
|
||||
const input = { milestoneId, projectPath: this.projectPath };
|
||||
await this.setActiveIssueMilestone(input);
|
||||
} catch (e) {
|
||||
this.setError({ error: e, message: this.$options.i18n.updateMilestoneError });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
milestone: __('Milestone'),
|
||||
noMilestone: __('No milestone'),
|
||||
assignMilestone: __('Assign milestone'),
|
||||
noMilestonesFound: s__('Milestones|No milestones found'),
|
||||
fetchMilestonesError: __('There was a problem fetching milestones.'),
|
||||
updateMilestoneError: __('An error occurred while updating the milestone.'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<board-editable-item
|
||||
ref="sidebarItem"
|
||||
:title="$options.i18n.milestone"
|
||||
:loading="loading"
|
||||
data-testid="sidebar-milestones"
|
||||
@open="handleOpen"
|
||||
@close="handleClose"
|
||||
>
|
||||
<template v-if="hasMilestone" #collapsed>
|
||||
<strong class="gl-text-gray-900">{{ activeBoardItem.milestone.title }}</strong>
|
||||
</template>
|
||||
<gl-dropdown
|
||||
ref="dropdown"
|
||||
:text="dropdownText"
|
||||
:header-text="$options.i18n.assignMilestone"
|
||||
block
|
||||
@hide="handleClose"
|
||||
>
|
||||
<gl-search-box-by-type ref="search" v-model.trim="searchTitle" class="gl-m-3" />
|
||||
<gl-dropdown-item
|
||||
data-testid="no-milestone-item"
|
||||
:is-check-item="true"
|
||||
:is-checked="!activeBoardItem.milestone"
|
||||
@click="setMilestone(null)"
|
||||
>
|
||||
{{ $options.i18n.noMilestone }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider />
|
||||
<gl-loading-icon v-if="$apollo.loading" class="gl-py-4" />
|
||||
<template v-else-if="milestones.length > 0">
|
||||
<gl-dropdown-item
|
||||
v-for="milestone in milestones"
|
||||
:key="milestone.id"
|
||||
:is-check-item="true"
|
||||
:is-checked="activeBoardItem.milestone && milestone.id === activeBoardItem.milestone.id"
|
||||
data-testid="milestone-item"
|
||||
@click="setMilestone(milestone.id)"
|
||||
>
|
||||
{{ milestone.title }}
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
<gl-dropdown-text v-else data-testid="no-milestones-found">
|
||||
{{ $options.i18n.noMilestonesFound }}
|
||||
</gl-dropdown-text>
|
||||
</gl-dropdown>
|
||||
</board-editable-item>
|
||||
</template>
|
||||
|
|
@ -14,10 +14,6 @@ fragment IssueNode on Issue {
|
|||
confidential
|
||||
webUrl
|
||||
relativePosition
|
||||
milestone {
|
||||
id
|
||||
title
|
||||
}
|
||||
assignees {
|
||||
nodes {
|
||||
...User
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
mutation issueSetMilestone($input: UpdateIssueInput!) {
|
||||
updateIssue(input: $input) {
|
||||
issue {
|
||||
milestone {
|
||||
id
|
||||
title
|
||||
description
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -12,11 +12,11 @@ query ListIssues(
|
|||
) {
|
||||
group(fullPath: $fullPath) @include(if: $isGroup) {
|
||||
board(id: $boardId) {
|
||||
lists(id: $id) {
|
||||
lists(id: $id, issueFilters: $filters) {
|
||||
nodes {
|
||||
id
|
||||
issuesCount
|
||||
issues(first: $first, filters: $filters, after: $after) {
|
||||
count
|
||||
edges {
|
||||
node {
|
||||
...IssueNode
|
||||
|
|
@ -33,11 +33,11 @@ query ListIssues(
|
|||
}
|
||||
project(fullPath: $fullPath) @include(if: $isProject) {
|
||||
board(id: $boardId) {
|
||||
lists(id: $id) {
|
||||
lists(id: $id, issueFilters: $filters) {
|
||||
nodes {
|
||||
id
|
||||
issuesCount
|
||||
issues(first: $first, filters: $filters, after: $after) {
|
||||
count
|
||||
edges {
|
||||
node {
|
||||
...IssueNode
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import groupProjectsQuery from '../graphql/group_projects.query.graphql';
|
|||
import issueCreateMutation from '../graphql/issue_create.mutation.graphql';
|
||||
import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.graphql';
|
||||
import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql';
|
||||
import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.graphql';
|
||||
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
|
|
@ -479,30 +478,6 @@ export default {
|
|||
});
|
||||
},
|
||||
|
||||
setActiveIssueMilestone: async ({ commit, getters }, input) => {
|
||||
const { activeBoardItem } = getters;
|
||||
const { data } = await gqlClient.mutate({
|
||||
mutation: issueSetMilestoneMutation,
|
||||
variables: {
|
||||
input: {
|
||||
iid: String(activeBoardItem.iid),
|
||||
milestoneId: getIdFromGraphQLId(input.milestoneId),
|
||||
projectPath: input.projectPath,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (data.updateIssue.errors?.length > 0) {
|
||||
throw new Error(data.updateIssue.errors);
|
||||
}
|
||||
|
||||
commit(types.UPDATE_BOARD_ITEM_BY_ID, {
|
||||
itemId: activeBoardItem.id,
|
||||
prop: 'milestone',
|
||||
value: data.updateIssue.issue.milestone,
|
||||
});
|
||||
},
|
||||
|
||||
addListItem: ({ commit }, { list, item, position, inProgress = false }) => {
|
||||
commit(types.ADD_BOARD_ITEM_TO_LIST, {
|
||||
listId: list.id,
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ export default {
|
|||
},
|
||||
triggerSource: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'unknown',
|
||||
required: true,
|
||||
},
|
||||
trackExperiment: {
|
||||
type: String,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
GlInfiniteScroll,
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import { produce } from 'immer';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
|
|
@ -22,7 +23,7 @@ import getCurrentBranch from '~/pipeline_editor/graphql/queries/client/current_b
|
|||
|
||||
export default {
|
||||
i18n: {
|
||||
dropdownHeader: s__('Switch Branch'),
|
||||
dropdownHeader: s__('Switch branch'),
|
||||
title: s__('Branches'),
|
||||
fetchError: s__('Unable to fetch branch list for this project.'),
|
||||
},
|
||||
|
|
@ -35,6 +36,9 @@ export default {
|
|||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
inject: ['projectFullPath', 'totalBranches'],
|
||||
props: {
|
||||
paginationLimit: {
|
||||
|
|
@ -180,6 +184,8 @@ export default {
|
|||
<template>
|
||||
<gl-dropdown
|
||||
v-if="showBranchSwitcher"
|
||||
v-gl-tooltip.hover
|
||||
:title="$options.i18n.dropdownHeader"
|
||||
:header-text="$options.i18n.dropdownHeader"
|
||||
:text="currentBranch"
|
||||
icon="branch"
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ export default {
|
|||
>
|
||||
<template #footer>
|
||||
<gl-dropdown-item v-if="directlyInviteMembers">
|
||||
<sidebar-invite-members />
|
||||
<sidebar-invite-members :issuable-type="issuableType" />
|
||||
</gl-dropdown-item> </template
|
||||
></user-select>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,17 @@ export default {
|
|||
components: {
|
||||
InviteMembersTrigger,
|
||||
},
|
||||
props: {
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
triggerSource() {
|
||||
return `${this.issuableType}-assignee-dropdown`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -18,6 +29,7 @@ export default {
|
|||
:display-text="$options.displayText"
|
||||
:event="$options.dataTrackEvent"
|
||||
:label="$options.dataTrackLabel"
|
||||
:trigger-source="triggerSource"
|
||||
classes="gl-display-block gl-pl-6 gl-hover-text-decoration-none gl-hover-text-blue-800!"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import 'mixins_and_variables_and_functions';
|
||||
|
||||
.escalation-policy-modal {
|
||||
width: 640px;
|
||||
}
|
||||
|
|
@ -9,3 +11,27 @@
|
|||
.rule-close-icon {
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
$stroke-size: 1px;
|
||||
|
||||
.right-arrow {
|
||||
@include gl-relative;
|
||||
@include gl-mx-5;
|
||||
@include gl-display-inline-block;
|
||||
@include gl-vertical-align-middle;
|
||||
height: $stroke-size;
|
||||
background-color: var(--gray-900, $gray-900);
|
||||
min-width: $gl-spacing-scale-7;
|
||||
|
||||
&-head {
|
||||
@include gl-absolute;
|
||||
top: -2*$stroke-size;
|
||||
left: calc(100% - #{5*$stroke-size});
|
||||
@include gl-display-inline-block;
|
||||
@include gl-p-1;
|
||||
@include gl-border-solid;
|
||||
border-width: 0 $stroke-size $stroke-size 0;
|
||||
border-color: var(--gray-900, $gray-900);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,13 @@ module Ci
|
|||
|
||||
belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id
|
||||
|
||||
default_value_for :data_store, :redis
|
||||
default_value_for :data_store do
|
||||
if Feature.enabled?(:dedicated_redis_trace_chunks, type: :ops)
|
||||
:redis_trace_chunks
|
||||
else
|
||||
:redis
|
||||
end
|
||||
end
|
||||
|
||||
after_create { metrics.increment_trace_operation(operation: :chunked) }
|
||||
|
||||
|
|
@ -25,22 +31,22 @@ module Ci
|
|||
|
||||
FailedToPersistDataError = Class.new(StandardError)
|
||||
|
||||
# Note: The ordering of this hash is related to the precedence of persist store.
|
||||
# The bottom item takes the highest precedence, and the top item takes the lowest precedence.
|
||||
DATA_STORES = {
|
||||
redis: 1,
|
||||
database: 2,
|
||||
fog: 3
|
||||
fog: 3,
|
||||
redis_trace_chunks: 4
|
||||
}.freeze
|
||||
|
||||
STORE_TYPES = DATA_STORES.keys.to_h do |store|
|
||||
[store, "Ci::BuildTraceChunks::#{store.capitalize}".constantize]
|
||||
[store, "Ci::BuildTraceChunks::#{store.to_s.camelize}".constantize]
|
||||
end.freeze
|
||||
LIVE_STORES = %i[redis redis_trace_chunks].freeze
|
||||
|
||||
enum data_store: DATA_STORES
|
||||
|
||||
scope :live, -> { redis }
|
||||
scope :persisted, -> { not_redis.order(:chunk_index) }
|
||||
scope :live, -> { where(data_store: LIVE_STORES) }
|
||||
scope :persisted, -> { where.not(data_store: LIVE_STORES).order(:chunk_index) }
|
||||
|
||||
class << self
|
||||
def all_stores
|
||||
|
|
@ -48,8 +54,7 @@ module Ci
|
|||
end
|
||||
|
||||
def persistable_store
|
||||
# get first available store from the back of the list
|
||||
all_stores.reverse.find { |store| get_store_class(store).available? }
|
||||
STORE_TYPES[:fog].available? ? :fog : :database
|
||||
end
|
||||
|
||||
def get_store_class(store)
|
||||
|
|
@ -195,7 +200,7 @@ module Ci
|
|||
end
|
||||
|
||||
def flushed?
|
||||
!redis?
|
||||
!live?
|
||||
end
|
||||
|
||||
def migrated?
|
||||
|
|
@ -203,7 +208,7 @@ module Ci
|
|||
end
|
||||
|
||||
def live?
|
||||
redis?
|
||||
LIVE_STORES.include?(data_store.to_sym)
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
module Ci
|
||||
module BuildTraceChunks
|
||||
class Database
|
||||
def available?
|
||||
true
|
||||
end
|
||||
|
||||
def keys(relation)
|
||||
[]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,10 +3,18 @@
|
|||
module Ci
|
||||
module BuildTraceChunks
|
||||
class Fog
|
||||
def available?
|
||||
def self.available?
|
||||
object_store.enabled
|
||||
end
|
||||
|
||||
def self.object_store
|
||||
Gitlab.config.artifacts.object_store
|
||||
end
|
||||
|
||||
def available?
|
||||
self.class.available?
|
||||
end
|
||||
|
||||
def data(model)
|
||||
files.get(key(model))&.body
|
||||
rescue Excon::Error::NotFound
|
||||
|
|
@ -85,7 +93,7 @@ module Ci
|
|||
end
|
||||
|
||||
def object_store
|
||||
Gitlab.config.artifacts.object_store
|
||||
self.class.object_store
|
||||
end
|
||||
|
||||
def object_store_raw_config
|
||||
|
|
|
|||
|
|
@ -2,92 +2,11 @@
|
|||
|
||||
module Ci
|
||||
module BuildTraceChunks
|
||||
class Redis
|
||||
CHUNK_REDIS_TTL = 1.week
|
||||
LUA_APPEND_CHUNK = <<~EOS
|
||||
local key, new_data, offset = KEYS[1], ARGV[1], ARGV[2]
|
||||
local length = new_data:len()
|
||||
local expire = #{CHUNK_REDIS_TTL.seconds}
|
||||
local current_size = redis.call("strlen", key)
|
||||
offset = tonumber(offset)
|
||||
|
||||
if offset == 0 then
|
||||
-- overwrite everything
|
||||
redis.call("set", key, new_data, "ex", expire)
|
||||
return redis.call("strlen", key)
|
||||
elseif offset > current_size then
|
||||
-- offset range violation
|
||||
return -1
|
||||
elseif offset + length >= current_size then
|
||||
-- efficiently append or overwrite and append
|
||||
redis.call("expire", key, expire)
|
||||
return redis.call("setrange", key, offset, new_data)
|
||||
else
|
||||
-- append and truncate
|
||||
local current_data = redis.call("get", key)
|
||||
new_data = current_data:sub(1, offset) .. new_data
|
||||
redis.call("set", key, new_data, "ex", expire)
|
||||
return redis.call("strlen", key)
|
||||
end
|
||||
EOS
|
||||
|
||||
def available?
|
||||
true
|
||||
end
|
||||
|
||||
def data(model)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.get(key(model))
|
||||
end
|
||||
end
|
||||
|
||||
def set_data(model, new_data)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.set(key(model), new_data, ex: CHUNK_REDIS_TTL)
|
||||
end
|
||||
end
|
||||
|
||||
def append_data(model, new_data, offset)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.eval(LUA_APPEND_CHUNK, keys: [key(model)], argv: [new_data, offset])
|
||||
end
|
||||
end
|
||||
|
||||
def size(model)
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis.strlen(key(model))
|
||||
end
|
||||
end
|
||||
|
||||
def delete_data(model)
|
||||
delete_keys([[model.build_id, model.chunk_index]])
|
||||
end
|
||||
|
||||
def keys(relation)
|
||||
relation.pluck(:build_id, :chunk_index)
|
||||
end
|
||||
|
||||
def delete_keys(keys)
|
||||
return if keys.empty?
|
||||
|
||||
keys = keys.map { |key| key_raw(*key) }
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/224171
|
||||
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
|
||||
redis.del(keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Redis < RedisBase
|
||||
private
|
||||
|
||||
def key(model)
|
||||
key_raw(model.build_id, model.chunk_index)
|
||||
end
|
||||
|
||||
def key_raw(build_id, chunk_index)
|
||||
"gitlab:ci:trace:#{build_id.to_i}:chunks:#{chunk_index.to_i}"
|
||||
def with_redis
|
||||
Gitlab::Redis::SharedState.with { |redis| yield(redis) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
module BuildTraceChunks
|
||||
class RedisBase
|
||||
CHUNK_REDIS_TTL = 1.week
|
||||
LUA_APPEND_CHUNK = <<~EOS
|
||||
local key, new_data, offset = KEYS[1], ARGV[1], ARGV[2]
|
||||
local length = new_data:len()
|
||||
local expire = #{CHUNK_REDIS_TTL.seconds}
|
||||
local current_size = redis.call("strlen", key)
|
||||
offset = tonumber(offset)
|
||||
|
||||
if offset == 0 then
|
||||
-- overwrite everything
|
||||
redis.call("set", key, new_data, "ex", expire)
|
||||
return redis.call("strlen", key)
|
||||
elseif offset > current_size then
|
||||
-- offset range violation
|
||||
return -1
|
||||
elseif offset + length >= current_size then
|
||||
-- efficiently append or overwrite and append
|
||||
redis.call("expire", key, expire)
|
||||
return redis.call("setrange", key, offset, new_data)
|
||||
else
|
||||
-- append and truncate
|
||||
local current_data = redis.call("get", key)
|
||||
new_data = current_data:sub(1, offset) .. new_data
|
||||
redis.call("set", key, new_data, "ex", expire)
|
||||
return redis.call("strlen", key)
|
||||
end
|
||||
EOS
|
||||
|
||||
def data(model)
|
||||
with_redis do |redis|
|
||||
redis.get(key(model))
|
||||
end
|
||||
end
|
||||
|
||||
def set_data(model, new_data)
|
||||
with_redis do |redis|
|
||||
redis.set(key(model), new_data, ex: CHUNK_REDIS_TTL)
|
||||
end
|
||||
end
|
||||
|
||||
def append_data(model, new_data, offset)
|
||||
with_redis do |redis|
|
||||
redis.eval(LUA_APPEND_CHUNK, keys: [key(model)], argv: [new_data, offset])
|
||||
end
|
||||
end
|
||||
|
||||
def size(model)
|
||||
with_redis do |redis|
|
||||
redis.strlen(key(model))
|
||||
end
|
||||
end
|
||||
|
||||
def delete_data(model)
|
||||
delete_keys([[model.build_id, model.chunk_index]])
|
||||
end
|
||||
|
||||
def keys(relation)
|
||||
relation.pluck(:build_id, :chunk_index)
|
||||
end
|
||||
|
||||
def delete_keys(keys)
|
||||
return if keys.empty?
|
||||
|
||||
keys = keys.map { |key| key_raw(*key) }
|
||||
|
||||
with_redis do |redis|
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/224171
|
||||
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
|
||||
redis.del(keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def key(model)
|
||||
key_raw(model.build_id, model.chunk_index)
|
||||
end
|
||||
|
||||
def key_raw(build_id, chunk_index)
|
||||
"gitlab:ci:trace:#{build_id.to_i}:chunks:#{chunk_index.to_i}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
module BuildTraceChunks
|
||||
class RedisTraceChunks < RedisBase
|
||||
private
|
||||
|
||||
def with_redis
|
||||
Gitlab::Redis::TraceChunks.with { |redis| yield(redis) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -16,7 +16,10 @@
|
|||
.gl-w-half.gl-xs-w-full
|
||||
.gl-display-flex.gl-flex-wrap.gl-justify-content-end.gl-mb-3
|
||||
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full', display_text: _('Invite a group') } }
|
||||
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
|
||||
.js-invite-members-trigger{ data: { variant: 'success',
|
||||
classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3',
|
||||
trigger_source: 'group-members-page',
|
||||
display_text: _('Invite members') } }
|
||||
= render 'groups/invite_members_modal', group: @group
|
||||
- if can_manage_members? && Feature.disabled?(:invite_members_group_modal, @group)
|
||||
%hr.gl-mt-4
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@
|
|||
- if @project.allowed_to_share_with_group?
|
||||
.js-invite-group-trigger{ data: { classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite a group') } }
|
||||
- if can_manage_project_members?(@project) && !membership_locked?
|
||||
.js-invite-members-trigger{ data: { variant: 'success', classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3', display_text: _('Invite members') } }
|
||||
.js-invite-members-trigger{ data: { variant: 'success',
|
||||
classes: 'gl-mt-3 gl-sm-w-auto gl-w-full gl-sm-ml-3',
|
||||
trigger_source: 'project-members-page',
|
||||
display_text: _('Invite members') } }
|
||||
= render 'projects/invite_members_modal', project: @project
|
||||
|
||||
- else
|
||||
|
|
|
|||
|
|
@ -45,4 +45,5 @@
|
|||
= render 'shared/issuable/sidebar_user_dropdown',
|
||||
options: options,
|
||||
wrapper_class: 'js-sidebar-assignee-dropdown',
|
||||
track_label: 'edit_assignee'
|
||||
track_label: 'edit_assignee',
|
||||
trigger_source: "#{issuable_type}-assignee-dropdown"
|
||||
|
|
|
|||
|
|
@ -42,4 +42,5 @@
|
|||
= render 'shared/issuable/sidebar_user_dropdown',
|
||||
options: options,
|
||||
wrapper_class: 'js-sidebar-reviewer-dropdown',
|
||||
track_label: 'edit_reviewer'
|
||||
track_label: 'edit_reviewer',
|
||||
trigger_source: "#{issuable_type}-reviewer-dropdown"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
.js-invite-members-trigger{ data: { trigger_element: 'anchor',
|
||||
display_text: _('Invite Members'),
|
||||
event: 'click_invite_members',
|
||||
trigger_source: local_assigns.fetch(:trigger_source),
|
||||
label: data['track-label'] } }
|
||||
- else
|
||||
= dropdown_tag(data['dropdown-title'], options: options)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: dedicated_redis_trace_chunks
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62938
|
||||
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1096
|
||||
milestone: '14.0'
|
||||
type: ops
|
||||
group: team::Scalability
|
||||
default_enabled: false
|
||||
|
|
@ -47,8 +47,8 @@ verification methods:
|
|||
| Blobs | Container registry _(file system)_ | Geo with API/Docker API | _Not implemented_ |
|
||||
| Blobs | Container registry _(object storage)_ | Geo with API/Managed/Docker API (*2*) | _Not implemented_ |
|
||||
| Blobs | Package registry _(file system)_ | Geo with API | SHA256 checksum |
|
||||
| Blobs | Package registry _(object storage)_ | Geo with API/Managed (*2*) | SHA256 checksum |
|
||||
| Blobs | Versioned Terraform State _(file system)_ | Geo with API | _Not implemented_ |
|
||||
| Blobs | Package registry _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
| Blobs | Versioned Terraform State _(file system)_ | Geo with API | SHA256 checksum |
|
||||
| Blobs | Versioned Terraform State _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
| Blobs | External Merge Request Diffs _(file system)_ | Geo with API | _Not implemented_ |
|
||||
| Blobs | External Merge Request Diffs _(object storage)_ | Geo with API/Managed (*2*) | _Not implemented_ |
|
||||
|
|
@ -190,14 +190,14 @@ successfully, you must replicate their data using some other means.
|
|||
|[Container Registry](../../packages/container_registry.md) | **Yes** (12.3) | No | No | Disabled by default. See [instructions](docker_registry.md) to enable. |
|
||||
|[Content in object storage (beta)](object_storage.md) | **Yes** (12.4) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/13845) | No | |
|
||||
|[Project designs repository](../../../user/project/issues/design_management.md) | **Yes** (12.7) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/32467) | Via Object Storage provider if supported. Native Geo support (Beta). | |
|
||||
|[Package Registry for npm](../../../user/packages/npm_registry/index.md) | **Yes** (13.2) | **Yes** (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for Maven](../../../user/packages/maven_repository/index.md) | **Yes** (13.2) | **Yes** (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for Conan](../../../user/packages/conan_repository/index.md) | **Yes** (13.2) | **Yes** (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for NuGet](../../../user/packages/nuget_repository/index.md) | **Yes** (13.2) | **Yes** (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for PyPI](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | **Yes** (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for Composer](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | **Yes** (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for generic packages](../../../user/packages/generic_packages/index.md) | **Yes** (13.5) | **Yes** (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_terraform_state_version_replication`, enabled by default. |
|
||||
|[Package Registry for npm](../../../user/packages/npm_registry/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for Maven](../../../user/packages/maven_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for Conan](../../../user/packages/conan_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for NuGet](../../../user/packages/nuget_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for PyPI](../../../user/packages/pypi_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for Composer](../../../user/packages/composer_repository/index.md) | **Yes** (13.2) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Package Registry for generic packages](../../../user/packages/generic_packages/index.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.10) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_package_file_replication`, enabled by default. |
|
||||
|[Versioned Terraform State](../../terraform_state.md) | **Yes** (13.5) | [**Yes**](#limitation-of-verification-for-files-in-object-storage) (13.12) | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_terraform_state_version_replication`, enabled by default. |
|
||||
|[External merge request diffs](../../merge_request_diffs.md) | **Yes** (13.5) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Behind feature flag `geo_merge_request_diff_replication`, enabled by default. |
|
||||
|[Versioned snippets](../../../user/snippets.md#versioned-snippets) | [**Yes** (13.7)](https://gitlab.com/groups/gitlab-org/-/epics/2809) | [No](https://gitlab.com/groups/gitlab-org/-/epics/2810) | No | |
|
||||
|[Server-side Git hooks](../../server_hooks.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/1867) | No | No | |
|
||||
|
|
@ -205,3 +205,9 @@ successfully, you must replicate their data using some other means.
|
|||
|[GitLab Pages](../../pages/index.md) | [No](https://gitlab.com/groups/gitlab-org/-/epics/589) | No | No | |
|
||||
|[Dependency proxy images](../../../user/packages/dependency_proxy/index.md) | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/259694) | No | No | Blocked on [Geo: Secondary Mimicry](https://gitlab.com/groups/gitlab-org/-/epics/1528). Note that replication of this cache is not needed for Disaster Recovery purposes because it can be recreated from external sources. |
|
||||
|[Vulnerability Export](../../../user/application_security/vulnerability_report/#export-vulnerability-details) | [Not planned](https://gitlab.com/groups/gitlab-org/-/epics/3111) | No | Via Object Storage provider if supported. Native Geo support (Beta). | Not planned because they are ephemeral and sensitive. They can be regenerated on demand. |
|
||||
|
||||
#### Limitation of verification for files in Object Storage
|
||||
|
||||
GitLab managed Object Storage replication support [is in beta](object_storage.md#enabling-gitlab-managed-object-storage-replication).
|
||||
|
||||
Locally stored files are verified but remote stored files are not.
|
||||
|
|
|
|||
|
|
@ -25,7 +25,20 @@ size.
|
|||
You are encouraged to first read through all the steps before executing them
|
||||
in your testing/production environment.
|
||||
|
||||
## PostgreSQL replication
|
||||
## Single instance database replication
|
||||
|
||||
A single instance database replication is easier to set up and still provides the same Geo capabilities
|
||||
as a clusterized alternative. It's useful for setups running on a single machine
|
||||
or trying to evaluate Geo for a future clusterized installation.
|
||||
|
||||
A single instance can be expanded to a clusterized version using Patroni, which is recommended for a
|
||||
highly available architecture.
|
||||
|
||||
Follow below the instructions on how to set up PostgreSQL replication as a single instance database.
|
||||
Alternatively, you can look at the [Multi-node database replication](#multi-node-database-replication)
|
||||
instructions on setting up replication with a Patroni cluster.
|
||||
|
||||
### PostgreSQL replication
|
||||
|
||||
The GitLab **primary** node where the write operations happen connects to
|
||||
the **primary** database server, and **secondary** nodes
|
||||
|
|
@ -48,7 +61,7 @@ WARNING:
|
|||
Geo works with streaming replication. Logical replication is not supported at this time.
|
||||
There is an [issue where support is being discussed](https://gitlab.com/gitlab-org/gitlab/-/issues/7420).
|
||||
|
||||
### Step 1. Configure the **primary** server
|
||||
#### Step 1. Configure the **primary** server
|
||||
|
||||
1. SSH into your GitLab **primary** server and login as root:
|
||||
|
||||
|
|
@ -77,11 +90,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
|
|||
|
||||
This command uses your defined `external_url` in `/etc/gitlab/gitlab.rb`.
|
||||
|
||||
1. GitLab 10.4 and up only: Do the following to make sure the `gitlab` database user has a password defined:
|
||||
|
||||
NOTE:
|
||||
Until FDW settings are removed in GitLab version 14.0, avoid using single or double quotes in the
|
||||
password for PostgreSQL as that leads to errors when reconfiguring.
|
||||
1. Define a password for the `gitlab` database user:
|
||||
|
||||
Generate a MD5 hash of the desired password:
|
||||
|
||||
|
|
@ -103,18 +112,28 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
|
|||
# must be present in all application nodes.
|
||||
gitlab_rails['db_password'] = '<your_password_here>'
|
||||
```
|
||||
|
||||
1. Define a password for the database [replication user](https://wiki.postgresql.org/wiki/Streaming_Replication).
|
||||
|
||||
1. Omnibus GitLab already has a [replication user](https://wiki.postgresql.org/wiki/Streaming_Replication)
|
||||
called `gitlab_replicator`. You must set the password for this user manually.
|
||||
You are prompted to enter a password:
|
||||
We will use the username defined in `/etc/gitlab/gitlab.rb` under the `postgresql['sql_replication_user']`
|
||||
setting. The default value is `gitlab_replicator`, but if you changed it to something else, adapt
|
||||
the instructions below.
|
||||
|
||||
Generate a MD5 hash of the desired password:
|
||||
|
||||
```shell
|
||||
gitlab-ctl set-replication-password
|
||||
gitlab-ctl pg-password-md5 gitlab_replicator
|
||||
# Enter password: <your_password_here>
|
||||
# Confirm password: <your_password_here>
|
||||
# 950233c0dfc2f39c64cf30457c3b7f1e
|
||||
```
|
||||
|
||||
This command also reads the `postgresql['sql_replication_user']` Omnibus
|
||||
setting in case you have changed `gitlab_replicator` username to something
|
||||
else.
|
||||
Edit `/etc/gitlab/gitlab.rb`:
|
||||
|
||||
```ruby
|
||||
# Fill with the hash generated by `gitlab-ctl pg-password-md5 gitlab_replicator`
|
||||
postgresql['sql_replication_password'] = '<md5_hash_of_your_password>'
|
||||
```
|
||||
|
||||
If you are using an external database not managed by Omnibus GitLab, you need
|
||||
to create the replicator user and define a password to it manually:
|
||||
|
|
@ -275,7 +294,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
|
|||
need it when setting up the **secondary** node! The certificate is not sensitive
|
||||
data.
|
||||
|
||||
### Step 2. Configure the **secondary** server
|
||||
#### Step 2. Configure the **secondary** server
|
||||
|
||||
1. SSH into your GitLab **secondary** server and login as root:
|
||||
|
||||
|
|
@ -376,6 +395,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
|
|||
## Database credentials password (defined previously in primary node)
|
||||
## - replicate same values here as defined in primary node
|
||||
##
|
||||
postgresql['sql_replication_password'] = '<md5_hash_of_your_password>'
|
||||
postgresql['sql_user_password'] = '<md5_hash_of_your_password>'
|
||||
gitlab_rails['db_password'] = '<your_password_here>'
|
||||
```
|
||||
|
|
@ -395,7 +415,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
|
|||
gitlab-ctl restart postgresql
|
||||
```
|
||||
|
||||
### Step 3. Initiate the replication process
|
||||
#### Step 3. Initiate the replication process
|
||||
|
||||
Below we provide a script that connects the database on the **secondary** node to
|
||||
the database on the **primary** node, replicates the database, and creates the
|
||||
|
|
@ -461,37 +481,37 @@ data before running `pg_basebackup`.
|
|||
|
||||
The replication process is now complete.
|
||||
|
||||
## PgBouncer support (optional)
|
||||
### PgBouncer support (optional)
|
||||
|
||||
[PgBouncer](https://www.pgbouncer.org/) may be used with GitLab Geo to pool
|
||||
PostgreSQL connections. We recommend using PgBouncer if you use GitLab in a
|
||||
high-availability configuration with a cluster of nodes supporting a Geo
|
||||
**primary** site and two other clusters of nodes supporting a Geo **secondary** site.
|
||||
One for the main database and the other for the tracking database. For more information,
|
||||
PostgreSQL connections, which can improve performance even when using in a
|
||||
single instance installation.
|
||||
|
||||
We recommend using PgBouncer if you use GitLab in a highly available
|
||||
configuration with a cluster of nodes supporting a Geo **primary** site and
|
||||
two other clusters of nodes supporting a Geo **secondary** site. One for the
|
||||
main database and the other for the tracking database. For more information,
|
||||
see [High Availability with Omnibus GitLab](../../postgresql/replication_and_failover.md).
|
||||
|
||||
## Patroni support
|
||||
## Multi-node database replication
|
||||
|
||||
Support for Patroni is intended to replace `repmgr` as a
|
||||
[highly available PostgreSQL solution](../../postgresql/replication_and_failover.md)
|
||||
on the primary node, but it can also be used for PostgreSQL HA on a secondary
|
||||
site. Similar to `repmgr`, using Patroni on a secondary node is optional.
|
||||
In GitLab 14.0, Patroni replaced `repmgr` as the supported
|
||||
[highly available PostgreSQL solution](../../postgresql/replication_and_failover.md).
|
||||
|
||||
Starting with GitLab 13.5, Patroni is available for _experimental_ use with Geo
|
||||
primary and secondary sites. Due to its experimental nature, Patroni support is
|
||||
subject to change without notice.
|
||||
NOTE:
|
||||
If you still haven't [migrated from repmgr to Patroni](#migrating-from-repmgr-to-patroni) you're highly advised to do so.
|
||||
|
||||
This experimental implementation has the following limitations:
|
||||
### Patroni support
|
||||
|
||||
- Whenever `gitlab-ctl reconfigure` runs on a Patroni Leader instance, there's a
|
||||
chance of the node be demoted due to the required short-time restart. To
|
||||
avoid this, you can pause auto-failover by running `gitlab-ctl patroni pause`.
|
||||
After a reconfigure, it resumes on its own.
|
||||
Patroni is the official replication management solution for Geo. It
|
||||
can be used to build a highly available cluster on the **primary** and a **secondary** Geo site.
|
||||
Using Patroni on a **secondary** site is optional and you don't have to use the same amount of
|
||||
nodes on each Geo site.
|
||||
|
||||
For instructions about how to set up Patroni on the primary site, see the
|
||||
[PostgreSQL replication and failover with Omnibus GitLab](../../postgresql/replication_and_failover.md#patroni) page.
|
||||
|
||||
### Configuring Patroni cluster for a Geo secondary site
|
||||
#### Configuring Patroni cluster for a Geo secondary site
|
||||
|
||||
In a Geo secondary site, the main PostgreSQL database is a read-only replica of the primary site’s PostgreSQL database.
|
||||
|
||||
|
|
@ -503,7 +523,7 @@ configuration for the secondary site. The internal load balancer provides a sing
|
|||
endpoint for connecting to the Patroni cluster's leader whenever a new leader is
|
||||
elected. Be sure to use [password credentials](../../postgresql/replication_and_failover.md#database-authorization-for-patroni) and other database best practices.
|
||||
|
||||
#### Step 1. Configure Patroni permanent replication slot on the primary site
|
||||
##### Step 1. Configure Patroni permanent replication slot on the primary site
|
||||
|
||||
To set up database replication with Patroni on a secondary node, we need to
|
||||
configure a _permanent replication slot_ on the primary node's Patroni cluster,
|
||||
|
|
@ -526,8 +546,8 @@ Leader instance**:
|
|||
retry_join: %w[CONSUL_PRIMARY1_IP CONSUL_PRIMARY2_IP CONSUL_PRIMARY3_IP]
|
||||
}
|
||||
|
||||
repmgr['enable'] = false
|
||||
|
||||
roles ['patroni_role']
|
||||
|
||||
# You need one entry for each secondary, with a unique name following PostgreSQL slot_name constraints:
|
||||
#
|
||||
# Configuration syntax is: 'unique_slotname' => { 'type' => 'physical' },
|
||||
|
|
@ -539,15 +559,18 @@ Leader instance**:
|
|||
patroni['use_pg_rewind'] = true
|
||||
patroni['postgresql']['max_wal_senders'] = 8 # Use double of the amount of patroni/reserved slots (3 patronis + 1 reserved slot for a Geo secondary).
|
||||
patroni['postgresql']['max_replication_slots'] = 8 # Use double of the amount of patroni/reserved slots (3 patronis + 1 reserved slot for a Geo secondary).
|
||||
patroni['replication_password'] = 'PLAIN_TEXT_POSTGRESQL_REPLICATION_PASSWORD'
|
||||
|
||||
postgresql['md5_auth_cidr_addresses'] = [
|
||||
'PATRONI_PRIMARY1_IP/32', 'PATRONI_PRIMARY2_IP/32', 'PATRONI_PRIMARY3_IP/32', 'PATRONI_PRIMARY_PGBOUNCER/32',
|
||||
'PATRONI_SECONDARY1_IP/32', 'PATRONI_SECONDARY2_IP/32', 'PATRONI_SECONDARY3_IP/32', 'PATRONI_SECONDARY_PGBOUNCER/32' # We list all secondary instances as they can all become a Standby Leader
|
||||
# We list all secondary instances as they can all become a Standby Leader
|
||||
postgresql['md5_auth_cidr_addresses'] = %w[
|
||||
PATRONI_PRIMARY1_IP/32 PATRONI_PRIMARY2_IP/32 PATRONI_PRIMARY3_IP/32 PATRONI_PRIMARY_PGBOUNCER/32
|
||||
PATRONI_SECONDARY1_IP/32 PATRONI_SECONDARY2_IP/32 PATRONI_SECONDARY3_IP/32 PATRONI_SECONDARY_PGBOUNCER/32
|
||||
]
|
||||
|
||||
postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH'
|
||||
postgresql['sql_replication_password'] = 'POSTGRESQL_REPLICATION_PASSWORD_HASH'
|
||||
postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
|
||||
postgresql['listen_address'] = '0.0.0.0' # You can use a public or VPC address here instead
|
||||
```
|
||||
|
||||
1. Reconfigure GitLab for the changes to take effect:
|
||||
|
|
@ -556,7 +579,7 @@ Leader instance**:
|
|||
gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
#### Step 2. Configure the internal load balancer on the primary site
|
||||
##### Step 2. Configure the internal load balancer on the primary site
|
||||
|
||||
To avoid reconfiguring the Standby Leader on the secondary site whenever a new
|
||||
Leader is elected on the primary site, we need to set up a TCP internal load
|
||||
|
|
@ -600,7 +623,7 @@ backend postgresql
|
|||
|
||||
Refer to your preferred Load Balancer's documentation for further guidance.
|
||||
|
||||
#### Step 3. Configure a PgBouncer node on the secondary site
|
||||
##### Step 3. Configure a PgBouncer node on the secondary site
|
||||
|
||||
A production-ready and highly available configuration requires at least
|
||||
three Consul nodes, a minimum of one PgBouncer node, but it’s recommended to have
|
||||
|
|
@ -624,19 +647,23 @@ Follow the minimal configuration for the PgBouncer node:
|
|||
roles ['pgbouncer_role']
|
||||
|
||||
# PgBouncer configuration
|
||||
pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
|
||||
pgbouncer['users'] = {
|
||||
'gitlab-consul': {
|
||||
# Generate it with: `gitlab-ctl pg-password-md5 gitlab-consul`
|
||||
password: 'GITLAB_CONSUL_PASSWORD_HASH'
|
||||
},
|
||||
'pgbouncer': {
|
||||
# Generate it with: `gitlab-ctl pg-password-md5 pgbouncer`
|
||||
password: 'PGBOUNCER_PASSWORD_HASH'
|
||||
}
|
||||
}
|
||||
|
||||
# Consul configuration
|
||||
consul['watchers'] = %w(postgresql)
|
||||
|
||||
consul['configuration'] = {
|
||||
retry_join: %w[CONSUL_SECONDARY1_IP CONSUL_SECONDARY2_IP CONSUL_SECONDARY3_IP]
|
||||
}
|
||||
|
||||
consul['monitoring_service_discovery'] = true
|
||||
```
|
||||
|
||||
|
|
@ -652,13 +679,13 @@ Follow the minimal configuration for the PgBouncer node:
|
|||
gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
|
||||
```
|
||||
|
||||
1. Restart the PgBouncer service:
|
||||
1. Reload the PgBouncer service:
|
||||
|
||||
```shell
|
||||
gitlab-ctl restart pgbouncer
|
||||
gitlab-ctl hup pgbouncer
|
||||
```
|
||||
|
||||
#### Step 4. Configure a Standby cluster on the secondary site
|
||||
##### Step 4. Configure a Standby cluster on the secondary site
|
||||
|
||||
NOTE:
|
||||
If you are converting a secondary site to a Patroni Cluster, you must start
|
||||
|
|
@ -676,21 +703,18 @@ For each Patroni instance on the secondary site:
|
|||
1. Edit `/etc/gitlab/gitlab.rb` and add the following:
|
||||
|
||||
```ruby
|
||||
roles ['consul_role', 'postgres_role']
|
||||
roles ['consul_role', 'patroni_role']
|
||||
|
||||
consul['enable'] = true
|
||||
consul['configuration'] = {
|
||||
retry_join: %w[CONSUL_SECONDARY1_IP CONSUL_SECONDARY2_IP CONSUL_SECONDARY3_IP]
|
||||
}
|
||||
|
||||
repmgr['enable'] = false
|
||||
|
||||
postgresql['md5_auth_cidr_addresses'] = [
|
||||
'PATRONI_SECONDARY1_IP/32', 'PATRONI_SECONDARY2_IP/32', 'PATRONI_SECONDARY3_IP/32', 'PATRONI_SECONDARY_PGBOUNCER/32',
|
||||
# Any other instance that needs access to the database as per documentation
|
||||
]
|
||||
|
||||
patroni['enable'] = false
|
||||
patroni['standby_cluster']['enable'] = true
|
||||
patroni['standby_cluster']['host'] = 'INTERNAL_LOAD_BALANCER_PRIMARY_IP'
|
||||
patroni['standby_cluster']['port'] = INTERNAL_LOAD_BALANCER_PRIMARY_PORT
|
||||
|
|
@ -699,6 +723,11 @@ For each Patroni instance on the secondary site:
|
|||
patroni['use_pg_rewind'] = true
|
||||
patroni['postgresql']['max_wal_senders'] = 5 # A minimum of three for one replica, plus two for each additional replica
|
||||
patroni['postgresql']['max_replication_slots'] = 5 # A minimum of three for one replica, plus two for each additional replica
|
||||
|
||||
postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH'
|
||||
postgresql['sql_replication_password'] = 'POSTGRESQL_REPLICATION_PASSWORD_HASH'
|
||||
postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
|
||||
postgresql['listen_address'] = '0.0.0.0' # You can use a public or VPC address here instead
|
||||
```
|
||||
|
||||
1. Reconfigure GitLab for the changes to take effect.
|
||||
|
|
@ -708,28 +737,6 @@ For each Patroni instance on the secondary site:
|
|||
gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
1. Remove the PostgreSQL data directory:
|
||||
|
||||
WARNING:
|
||||
If you are converting a secondary site to a Patroni Cluster, you must skip
|
||||
this step on the PostgreSQL instance.
|
||||
|
||||
```shell
|
||||
rm -rf /var/opt/gitlab/postgresql/data
|
||||
```
|
||||
|
||||
1. Edit `/etc/gitlab/gitlab.rb` to enable Patroni:
|
||||
|
||||
```ruby
|
||||
patroni['enable'] = true
|
||||
```
|
||||
|
||||
1. Reconfigure GitLab for the changes to take effect:
|
||||
|
||||
```shell
|
||||
gitlab-ctl reconfigure
|
||||
```
|
||||
|
||||
### Migrating from repmgr to Patroni
|
||||
|
||||
1. Before migrating, it is recommended that there is no replication lag between the primary and secondary sites and that replication is paused. In GitLab 13.2 and later, you can pause and resume replication with `gitlab-ctl geo-replication-pause` and `gitlab-ctl geo-replication-resume` on a Geo secondary database node.
|
||||
|
|
|
|||
|
|
@ -1158,7 +1158,7 @@ POST /projects
|
|||
| `path` | string | **{check-circle}** Yes (if name isn't provided) | Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes). |
|
||||
| `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. |
|
||||
| `analytics_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private` or `enabled` |
|
||||
| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. |
|
||||
| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. To configure approval rules, see [Merge request approvals API](merge_request_approvals.md). |
|
||||
| `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. |
|
||||
| `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`). |
|
||||
| `auto_devops_enabled` | boolean | **{dotted-circle}** No | Enable Auto DevOps for this project. |
|
||||
|
|
@ -1233,7 +1233,7 @@ POST /projects/user/:user_id
|
|||
| `name` | string | **{check-circle}** Yes | The name of the new project. |
|
||||
| `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. |
|
||||
| `analytics_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private` or `enabled` |
|
||||
| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. |
|
||||
| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge requests by default. To configure approval rules, see [Merge request approvals API](merge_request_approvals.md). |
|
||||
| `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. |
|
||||
| `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`). |
|
||||
| `auto_devops_enabled` | boolean | **{dotted-circle}** No | Enable Auto DevOps for this project. |
|
||||
|
|
@ -1305,7 +1305,7 @@ PUT /projects/:id
|
|||
|-------------------------------------------------------------|----------------|------------------------|-------------|
|
||||
| `allow_merge_on_skipped_pipeline` | boolean | **{dotted-circle}** No | Set whether or not merge requests can be merged with skipped jobs. |
|
||||
| `analytics_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private` or `enabled` |
|
||||
| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge request by default. |
|
||||
| `approvals_before_merge` **(PREMIUM)** | integer | **{dotted-circle}** No | How many approvers should approve merge request by default. To configure approval rules, see [Merge request approvals API](merge_request_approvals.md). |
|
||||
| `auto_cancel_pending_pipelines` | string | **{dotted-circle}** No | Auto-cancel pending pipelines. This isn't a boolean, but enabled/disabled. |
|
||||
| `auto_devops_deploy_strategy` | string | **{dotted-circle}** No | Auto Deploy strategy (`continuous`, `manual`, or `timed_incremental`). |
|
||||
| `auto_devops_enabled` | boolean | **{dotted-circle}** No | Enable Auto DevOps for this project. |
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ see which pipelines are green and which are red allowing you to
|
|||
diagnose if there is a block at a particular point, or if there's
|
||||
a more systemic problem you need to investigate.
|
||||
|
||||
You can access the dashboard from the top bar by clicking
|
||||
**More > Environments**.
|
||||
You can access the dashboard on the top bar by selecting
|
||||
**Menu > Environments**.
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ If you are migrating from another CI/CD tool, view this documentation:
|
|||
- [Migrate from Jenkins](../migration/jenkins.md).
|
||||
|
||||
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch [First time GitLab & CI/CD](https://www.youtube.com/watch?v=kTNfi5z6Uvk&t=553s). This includes a quick introduction to GitLab, the first steps with CI/CD, building a Go project, running tests, using the CI/CD pipeline editor, detecting secrets and security vulnerabilities and offers more exercises for async practice.
|
||||
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch [Intro to GitLab CI](https://www.youtube.com/watch?v=l5705U8s_nQ&t=358s). This workshop uses the Web IDE to quickly get going with building source code using CI/CD, and run unit tests.
|
||||
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> Watch [Intro to GitLab CI](https://www.youtube.com/watch?v=l5705U8s_nQ&t=358s). This workshop uses the Web IDE to quickly get going with building source code using CI/CD, and run unit tests.
|
||||
|
||||
## CI/CD process overview
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ All of this is defined in the `.gitlab-ci.yml` file.
|
|||
|
||||
To create a `.gitlab-ci.yml` file:
|
||||
|
||||
1. Go to **Project overview > Details**.
|
||||
1. On the left sidebar, select **Project information > Details**.
|
||||
1. Above the file list, select the branch you want to commit to,
|
||||
click the plus icon, then select **New file**:
|
||||
|
||||
|
|
|
|||
|
|
@ -1069,36 +1069,42 @@ document to ensure it links to the most recent version of the file.
|
|||
|
||||
## Navigation
|
||||
|
||||
When documenting navigation through the user interface:
|
||||
|
||||
- Use the exact wording as shown in the UI, including any capital letters as-is.
|
||||
- Use bold text for navigation items.
|
||||
When documenting navigation through the user interface, use these terms and styles.
|
||||
|
||||
### What to call the menus
|
||||
|
||||
Use these terms when referring to the main GitLab user interface
|
||||
elements:
|
||||
|
||||
- **Top menu**: This is the top menu that spans the width of the user interface.
|
||||
It includes the GitLab logo, search field, counters, and the user's avatar.
|
||||
- **Top bar**: This is the top bar that spans the width of the user interface.
|
||||
It includes the menu, the GitLab logo, search field, counters, and the user's avatar.
|
||||
- **Left sidebar**: This is the navigation sidebar on the left of the user
|
||||
interface, specific to the project or group.
|
||||
- **Right sidebar**: This is the navigation sidebar on the right of the user
|
||||
interface, specific to the open issue, merge request, or epic.
|
||||
|
||||
### How to document the left sidebar
|
||||
### How to document the menus
|
||||
|
||||
To be consistent, use this format when you refer to the left sidebar.
|
||||
To be consistent, use this format when you write about UI navigation.
|
||||
|
||||
- Go to your project and select **Settings > CI/CD**.
|
||||
- Go to your group and select **Settings > CI/CD**.
|
||||
- Go to the Admin Area (**{admin}**) and select **Overview > Projects**.
|
||||
|
||||
For expandable menus, use this format:
|
||||
|
||||
1. Go to your group and select **Settings > CI/CD**.
|
||||
1. On the top bar, select **Menu > Project** and find your project.
|
||||
1. On the left sidebar, select **Settings > CI/CD**.
|
||||
1. Expand **General pipelines**.
|
||||
|
||||
Another example:
|
||||
|
||||
1. On the top bar, select **Menu > Group** and find your group.
|
||||
1. On the left sidebar, select **Settings > CI/CD**.
|
||||
1. Expand **General pipelines**.
|
||||
|
||||
An Admin Area example:
|
||||
|
||||
`1. On the top bar, select **Menu >** **{admin}** **Admin**.`
|
||||
|
||||
This text generates this HTML:
|
||||
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
|
||||
## Images
|
||||
|
||||
Images, including screenshots, can help a reader better understand a concept.
|
||||
|
|
@ -1377,10 +1383,10 @@ readability of the text.
|
|||
For example, this Markdown adds little to the accompanying text:
|
||||
|
||||
```markdown
|
||||
1. Go to **{home}** **Project overview > Details**.
|
||||
1. Go to **{home}** **Project information > Details**.
|
||||
```
|
||||
|
||||
1. Go to **{home}** **Project overview > Details**.
|
||||
1. Go to **{home}** **Project information > Details**.
|
||||
|
||||
However, these tables might help the reader connect the text to the user
|
||||
interface:
|
||||
|
|
|
|||
|
|
@ -163,14 +163,7 @@ stageGroupDashboards.dashboard('product_planning')
|
|||
.stageGroupDashboardTrailer()
|
||||
```
|
||||
|
||||
We provide basic customization to filter out the components essential to your group's activities. By default, all components `web`, `api`, `git`, and `sidekiq` are available in the dashboard. We can change this to only show `web` and `api`, or only show `sidekiq`:
|
||||
|
||||
```jsonnet
|
||||
stageGroupDashboards.dashboard('product_planning', components=['web', 'api']).stageGroupDashboardTrailer()
|
||||
# Or
|
||||
stageGroupDashboards.dashboard('product_planning', components=['sidekiq']).stageGroupDashboardTrailer()
|
||||
|
||||
```
|
||||
We provide basic customization to filter out the components essential to your group's activities. By default, only the `web`, `api`, and `sidekiq` components are available in the dashboard, while `git` is hidden. See [how to enable available components and optional graphs](#optional-graphs).
|
||||
|
||||
You can also append further information or custom metrics to a dashboard. This is an example that adds some links and a total request rate on the top of the page:
|
||||
|
||||
|
|
@ -219,3 +212,31 @@ If you want to see the workflow in action, we've recorded a pairing session on c
|
|||
available on [GitLab Unfiltered](https://youtu.be/shEd_eiUjdI).
|
||||
|
||||
For deeper customization and more complicated metrics, visit the [Grafonnet lib](https://github.com/grafana/grafonnet-lib) project and the [GitLab Prometheus Metrics](../administration/monitoring/prometheus/gitlab_metrics.md#gitlab-prometheus-metrics) documentation.
|
||||
|
||||
### Optional Graphs
|
||||
|
||||
Some Graphs aren't relevant for all groups, so they aren't added to
|
||||
the dashboard by default. They can be added by customizing the
|
||||
dashboard.
|
||||
|
||||
By default, only the `web`, `api`, and `sidekiq` metrics are
|
||||
shown. If you wish to see the metrics from the `git` fleet (or any
|
||||
other component that might be added in the future), this could be
|
||||
configured as follows:
|
||||
|
||||
```jsonnet
|
||||
stageGroupDashboards
|
||||
.dashboard('source_code', components=stageGroupDashboards.supportedComponents)
|
||||
.stageGroupDashboardTrailer()
|
||||
```
|
||||
|
||||
If your group is interested in Sidekiq job durations and their
|
||||
thresholds, these graphs can be added by calling the
|
||||
`.addSidekiqJobDurationByUrgency` function:
|
||||
|
||||
```jsonnet
|
||||
stageGroupDashboards
|
||||
.dashboard('access')
|
||||
.addSidekiqJobDurationByUrgency()
|
||||
.stageGroupDashboardTrailer()
|
||||
```
|
||||
|
|
|
|||
|
|
@ -165,12 +165,12 @@ To change the password for this customers portal account:
|
|||
1. Make the required changes to the **Your password** section.
|
||||
1. Click **Save changes**.
|
||||
|
||||
## Community program subscriptions
|
||||
## Community program subscriptions
|
||||
|
||||
### GitLab for Education
|
||||
|
||||
For qualifying non-profit educational institutions, the [GitLab for Education](https://about.gitlab.com/solutions/education/) program provides
|
||||
the top GitLab tier, plus 50,000 CI minutes per month.
|
||||
For qualifying non-profit educational institutions, the [GitLab for Education](https://about.gitlab.com/solutions/education/) program provides
|
||||
the top GitLab tier, plus 50,000 CI minutes per month.
|
||||
|
||||
The GitLab for Education license can only be used for instructional-use or
|
||||
non-commercial academic research.
|
||||
|
|
@ -180,8 +180,8 @@ Find more information on how to apply and renew at
|
|||
|
||||
### GitLab for Open Source
|
||||
|
||||
For qualifying open source projects, the [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/) program provides
|
||||
the top GitLab tier, plus 50,000 CI minutes per month.
|
||||
For qualifying open source projects, the [GitLab for Open Source](https://about.gitlab.com/solutions/open-source/) program provides
|
||||
the top GitLab tier, plus 50,000 CI minutes per month.
|
||||
|
||||
You can find more information about the [program requirements](https://about.gitlab.com/solutions/open-source/join/#requirements),
|
||||
[renewals](https://about.gitlab.com/solutions/open-source/join/$renewals),
|
||||
|
|
@ -211,13 +211,13 @@ After you ensure that you are using OSI-approved licenses for your projects, you
|
|||
|
||||
###### License overview
|
||||
|
||||
Go to **Project Overview > Details**. Take a screenshot that includes a view of the license you've chosen for your project.
|
||||
On the left sidebar, select **Project Information > Details**. Take a screenshot that includes a view of the license you've chosen for your project.
|
||||
|
||||

|
||||
|
||||
###### License file
|
||||
|
||||
Navigate to one of the license files that you uploaded. You can usually find the license file by selecting **Project Overview > Details** and scanning the page for the license.
|
||||
Navigate to one of the license files that you uploaded. You can usually find the license file by selecting **Project Information > Details** and scanning the page for the license.
|
||||
Make sure the screenshot includes the title of the license.
|
||||
|
||||

|
||||
|
|
@ -230,7 +230,7 @@ As a result, we ask that all projects under this license are publicly visible.
|
|||
|
||||
Follow these instructions to take a screenshot of the publicly visible settings:
|
||||
|
||||
1. Go to your project and select **Settings**.
|
||||
1. Go to your project and select **Settings**.
|
||||
1. Expand **Visibility, project features, permissions**.
|
||||
1. Set **Project Visibility** to **Public**.
|
||||
1. Ensure others can request access by selecting the **Users can request access** checkbox.
|
||||
|
|
@ -241,14 +241,14 @@ Follow these instructions to take a screenshot of the publicly visible settings:
|
|||
|
||||
NOTE:
|
||||
From time to time, GitLab allows exceptions. One or two projects within a group can be private if there is a legitimate need for it, for example,
|
||||
if a project holds sensitive data. Email `opensource@gitlab.com` with details of your use case to request written permission for exceptions.
|
||||
if a project holds sensitive data. Email `opensource@gitlab.com` with details of your use case to request written permission for exceptions.
|
||||
|
||||
### GitLab for Startups
|
||||
|
||||
For qualifying startups, the [GitLab for Startups](https://about.gitlab.com/solutions/startups/) program provides
|
||||
the top GitLab tier, plus 50,000 CI minutes per month for 12 months.
|
||||
For qualifying startups, the [GitLab for Startups](https://about.gitlab.com/solutions/startups/) program provides
|
||||
the top GitLab tier, plus 50,000 CI minutes per month for 12 months.
|
||||
|
||||
For more information, including program requirements, see the [Startup program's landing page](https://about.gitlab.com/solutions/startups/).
|
||||
For more information, including program requirements, see the [Startup program's landing page](https://about.gitlab.com/solutions/startups/).
|
||||
|
||||
Send all questions and requests related to the GitLab for Startups program to `startups@gitlab.com`.
|
||||
|
||||
|
|
@ -257,7 +257,7 @@ Send all questions and requests related to the GitLab for Startups program to `s
|
|||
Because these Community Programs are free of cost, regular Priority Support is not included. However, it can be purchased at a 95% discount in some cases.
|
||||
If interested, email the relevant community program team: `education@gitlab.com`, `opensource@gitlab.com`, or `startups@gitlab.com`.
|
||||
|
||||
As a community member, you can follow this diagram to find support:
|
||||
As a community member, you can follow this diagram to find support:
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -150,8 +150,7 @@ the following:
|
|||
|
||||

|
||||
|
||||
To view the Security Center, from the navigation bar at the top of the page, select
|
||||
**More > Security**.
|
||||
To view the Security Center, on the top bar, select **Menu > Security**.
|
||||
|
||||
### Adding projects to the Security Center
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
The Operations Dashboard provides a summary of each project's operational health,
|
||||
including pipeline and alert status.
|
||||
|
||||
The dashboard can be accessed from the top bar, by clicking **More > Operations**.
|
||||
To access the dashboard, on the top bar, select **Menu > Operations**.
|
||||
|
||||
## Adding a project to the dashboard
|
||||
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ To view the published package, go to **Packages & Registries > Package Registry*
|
|||
|
||||
A more detailed Composer CI/CD file is also available as a `.gitlab-ci.yml` template:
|
||||
|
||||
1. On the left sidebar, click **Project overview**.
|
||||
1. On the left sidebar, select **Project information**.
|
||||
1. Above the file list, click **Set up CI/CD**. If this button is not available, select **CI/CD Configuration** and then **Edit**.
|
||||
1. From the **Apply a template** list, select **Composer**.
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ of each package management system to publish different package types to the same
|
|||
|
||||
Let's take a look at how you might create a public place to hold all of your public packages.
|
||||
|
||||
1. Create a new project in GitLab. The project doesn't require any code or content. Note the project ID
|
||||
that's displayed on the project overview page.
|
||||
1. Create a new project in GitLab. The project doesn't require any code or content.
|
||||
1. On the left sidebar, select **Project information**, and note the project ID.
|
||||
1. Create an access token. All package types in the Package Registry are accessible by using
|
||||
[GitLab personal access tokens](../../profile/personal_access_tokens.md).
|
||||
If you're using CI/CD, you can use CI job tokens (`CI_JOB_TOKEN`) to authenticate.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ points to. Examples for badges can be the [pipeline status](../../ci/pipelines/s
|
|||
[test coverage](../../ci/pipelines/settings.md#test-coverage-report-badge), or ways to contact the
|
||||
project maintainers.
|
||||
|
||||

|
||||

|
||||
|
||||
## Project badges
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ Projects include the following [features](https://about.gitlab.com/features/):
|
|||
- [Security Dashboard](../application_security/security_dashboard/index.md) **(ULTIMATE)**
|
||||
- [Syntax highlighting](highlighting.md): Customize
|
||||
your code blocks, overriding the default language choice.
|
||||
- [Badges](badges.md): Add an image to the project overview.
|
||||
- [Badges](badges.md): Add an image to the **Project information** page.
|
||||
- [Releases](releases/index.md): Take a snapshot of
|
||||
the source, build output, metadata, and artifacts
|
||||
associated with a released version of your code.
|
||||
|
|
|
|||
|
|
@ -24,19 +24,17 @@ of the merge request.
|
|||
## Enabling commit edits from upstream members
|
||||
|
||||
In [GitLab 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/23308),
|
||||
this setting is enabled by default. It can be changed by users with Developer
|
||||
permissions to the source project. Once enabled, upstream members can
|
||||
retry the pipelines and jobs of the merge request:
|
||||
this setting is enabled by default. It can be changed by users with the
|
||||
Developer [role](../../permissions.md) for the source project. After it's enabled,
|
||||
upstream members can retry the pipelines and jobs of the merge request:
|
||||
|
||||
1. While creating or editing a merge request, select the checkbox **Allow
|
||||
commits from members who can merge to the target branch**.
|
||||
1. While creating or editing a merge request, scroll to **Contribution** and
|
||||
then select the **Allow commits from members who can merge to the target branch**.
|
||||
checkbox.
|
||||
1. Finish creating your merge request.
|
||||
|
||||

|
||||
|
||||
1. Once the merge request is created, you can see that commits from members who
|
||||
can merge to the target branch are allowed.
|
||||
|
||||

|
||||
After you create the merge request, the merge request widget displays a message:
|
||||
**Members who can merge are allowed to add commits.**
|
||||
|
||||
## Pushing to the fork as the upstream member
|
||||
|
||||
|
|
@ -48,41 +46,39 @@ Assuming that:
|
|||
- The forked project URL is `git@gitlab.com:thedude/awesome-project.git`.
|
||||
- The branch of the merge request is `update-docs`.
|
||||
|
||||
Here's how the process would look like:
|
||||
To find and work with the changes from the fork:
|
||||
|
||||
1. First, you need to get the changes that the merge request has introduced.
|
||||
Click the **Check out branch** button that has some pre-populated
|
||||
commands that you can run.
|
||||
|
||||

|
||||
|
||||
1. Use the copy button to copy the first command and paste them
|
||||
in your terminal:
|
||||
1. Open the merge request page, and select the **Overview** tab.
|
||||
1. Scroll to the merge request widget, and select **Check out branch**:
|
||||

|
||||
1. In the modal window, select **{copy-to-clipboard}** (**Copy**) for step 1
|
||||
to copy the `git fetch` and `git checkout` instructions to your clipboard.
|
||||
Paste the commands (which look like this example) into your terminal:
|
||||
|
||||
```shell
|
||||
git fetch git@gitlab.com:thedude/awesome-project.git update-docs
|
||||
git checkout -b thedude-awesome-project-update-docs FETCH_HEAD
|
||||
```
|
||||
|
||||
This fetches the branch of the forked project and then create a local branch
|
||||
These commands fetch the branch from the forked project, and create a local branch
|
||||
based off the fetched branch.
|
||||
|
||||
1. Make any changes you want and commit.
|
||||
1. Push to the forked project:
|
||||
1. Make your changes to the local copy of the branch, and then commit them.
|
||||
1. In your terminal, push your local changes back up to the forked project. This
|
||||
command pushes the local branch `thedude-awesome-project-update-docs` to the
|
||||
`update-docs` branch of the `git@gitlab.com:thedude/awesome-project.git` repository:
|
||||
|
||||
```shell
|
||||
git push git@gitlab.com:thedude/awesome-project.git thedude-awesome-project-update-docs:update-docs
|
||||
```
|
||||
|
||||
Note the colon (`:`) between the two branches. The above command pushes the
|
||||
local branch `thedude-awesome-project-update-docs` to the
|
||||
`update-docs` branch of the `git@gitlab.com:thedude/awesome-project.git` repository.
|
||||
Note the colon (`:`) between the two branches.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Pipeline status unavailable from MR page of forked project
|
||||
|
||||
When a user forks a project, the permissions on the forked copy are not copied over
|
||||
When a user forks a project, the permissions of the forked copy are not copied
|
||||
from the original project. The creator of the fork must grant permissions to the
|
||||
forked copy before members in the upstream project can view or merge the changes
|
||||
in the merge request.
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ for more control of the level of oversight and security your project needs, incl
|
|||
- [Require security team approval.](settings.md#security-approvals-in-merge-requests)
|
||||
|
||||
You can configure your merge request approval rules and settings through the GitLab
|
||||
user interface or [with the API](../../../../api/merge_request_approvals.md).
|
||||
user interface or with the [Merge request approvals API](../../../../api/merge_request_approvals.md).
|
||||
|
||||
## Approve a merge request
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
|
|
@ -38,8 +38,8 @@ You can assign **group milestones** to any issue or merge request of any project
|
|||
To view the group milestone list, in a group, go to **{issues}** **Issues > Milestones**.
|
||||
|
||||
You can also view all milestones you have access to in the dashboard milestones list.
|
||||
To view both project milestones and group milestones you have access to, select **More > Milestones**
|
||||
on the top navigation bar.
|
||||
To view both project milestones and group milestones you have access to, select **Menu > Milestones**
|
||||
on the top bar.
|
||||
|
||||
For information about project and group milestones API, see:
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Your GitLab repository should contain files specific to an SSG, or plain HTML.
|
|||
After you complete these steps, you may need to do additional
|
||||
configuration for the Pages site to generate properly.
|
||||
|
||||
1. In the left sidebar, click **Project overview**.
|
||||
1. On the left sidebar, select **Project information**.
|
||||
1. Click **Set up CI/CD**.
|
||||
|
||||

|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ and attach [release assets](#release-assets), like runbooks or packages.
|
|||
|
||||
To view a list of releases:
|
||||
|
||||
- Go to **Project overview > Releases**, or
|
||||
- On the left sidebar, select **Project information > Releases**, or
|
||||
|
||||
- On the project's overview page, if at least one release exists, click the number of releases.
|
||||
|
||||
|
|
@ -64,8 +64,7 @@ Read more about [Release permissions](../../../user/permissions.md#project-membe
|
|||
|
||||
To create a new release through the GitLab UI:
|
||||
|
||||
1. Navigate to **Project overview > Releases** and click the **New release**
|
||||
button.
|
||||
1. On the left sidebar, select **Project information > Releases** and select **New release**.
|
||||
1. Open the [**Tag name**](#tag-name) dropdown. Select an existing tag or type
|
||||
in a new tag name. Selecting an existing tag that is already associated with
|
||||
a release will result in a validation error.
|
||||
|
|
@ -105,7 +104,7 @@ Read more about [Release permissions](../../../user/permissions.md#project-membe
|
|||
|
||||
To edit the details of a release:
|
||||
|
||||
1. Navigate to **Project overview > Releases**.
|
||||
1. On the left sidebar, select **Project information > Releases**.
|
||||
1. In the top-right corner of the release you want to modify, click **Edit this release** (the pencil icon).
|
||||
1. On the **Edit Release** page, change the release's details.
|
||||
1. Click **Save changes**.
|
||||
|
|
@ -151,12 +150,12 @@ the [Releases API](../../../api/releases/index.md#create-a-release).
|
|||
|
||||
In the user interface, to associate milestones to a release:
|
||||
|
||||
1. Navigate to **Project overview > Releases**.
|
||||
1. On the left sidebar, select **Project information > Releases**.
|
||||
1. In the top-right corner of the release you want to modify, click **Edit this release** (the pencil icon).
|
||||
1. From the **Milestones** list, select each milestone you want to associate. You can select multiple milestones.
|
||||
1. Click **Save changes**.
|
||||
|
||||
On the **Project overview > Releases** page, the **Milestone** is listed in the top
|
||||
On the **Project information > Releases** page, the **Milestone** is listed in the top
|
||||
section, along with statistics about the issues in the milestones.
|
||||
|
||||

|
||||
|
|
@ -176,7 +175,7 @@ You can be notified by email when a new release is created for your project.
|
|||
|
||||
To subscribe to notifications for releases:
|
||||
|
||||
1. Navigate to **Project overview**.
|
||||
1. On the left sidebar, select **Project information**.
|
||||
1. Click **Notification setting** (the bell icon).
|
||||
1. In the list, click **Custom**.
|
||||
1. Select the **New release** check box.
|
||||
|
|
@ -210,7 +209,7 @@ deploy_to_production:
|
|||
To set a deploy freeze window in the UI, complete these steps:
|
||||
|
||||
1. Sign in to GitLab as a user with the [Maintainer role](../../permissions.md).
|
||||
1. Navigate to **Project overview**.
|
||||
1. On the left sidebar, select **Project information**.
|
||||
1. In the left navigation menu, navigate to **Settings > CI/CD**.
|
||||
1. Scroll to **Deploy freezes**.
|
||||
1. Click **Expand** to see the deploy freeze table.
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ You can download the source code that's stored in a repository.
|
|||
## Repository languages
|
||||
|
||||
For the default branch of each repository, GitLab determines which programming languages
|
||||
are used. This information is displayed on the project overview page.
|
||||
are used. This information is displayed on the **Project information** page.
|
||||
|
||||

|
||||
|
||||
|
|
@ -121,7 +121,7 @@ When new files are added, this information can take up to five minutes to update
|
|||
|
||||
### Add repository languages
|
||||
|
||||
Not all files are detected and listed on the project overview page. Documentation,
|
||||
Not all files are detected and listed on the **Project information** page. Documentation,
|
||||
vendor code, and most markup languages are excluded.
|
||||
|
||||
You can change this behavior by overriding the default settings.
|
||||
|
|
@ -200,7 +200,7 @@ To render an OpenAPI file:
|
|||
|
||||
## Repository size
|
||||
|
||||
The project overview page shows the size of all files in the repository. The size is
|
||||
The **Project information** page shows the size of all files in the repository. The size is
|
||||
updated, at most, every 15 minutes. The file size includes repository files, artifacts, and LFS.
|
||||
|
||||
The size can differ slightly from one instance to another due to compression, housekeeping, and other factors.
|
||||
|
|
|
|||
|
|
@ -228,10 +228,16 @@ Read through the documentation on [project settings](settings/index.md).
|
|||
|
||||
## Project activity
|
||||
|
||||
To view the activity of a project, navigate to **Project overview > Activity**.
|
||||
From there, you can click on the tabs to see **All** the activity, or see it
|
||||
filtered by **Push events**, **Merge events**, **Issue events**, **Comments**,
|
||||
**Team**, and **Wiki**.
|
||||
To view the activity of a project:
|
||||
|
||||
1. On the left sidebar, select **Project information > Activity**.
|
||||
1. Select a tab to view **All** the activity, or to filter it by any of these criteria:
|
||||
- **Push events**
|
||||
- **Merge events**
|
||||
- **Issue events**
|
||||
- **Comments**
|
||||
- **Team**
|
||||
- **Wiki**
|
||||
|
||||
### Leave a project
|
||||
|
||||
|
|
|
|||
|
|
@ -57,11 +57,11 @@ In GitLab versions 13.0 and later, snippets are [versioned by default](#versione
|
|||
|
||||
To discover all snippets visible to you in GitLab, you can:
|
||||
|
||||
- **View all snippets visible to you**: In the top navigation bar of your GitLab
|
||||
instance, go to **More > Snippets** to view your snippets dashboard.
|
||||
- **View all snippets visible to you**: On the top bar of your GitLab
|
||||
instance, select **Menu > Snippets** to view your snippets dashboard.
|
||||
- **Visit [GitLab snippets](https://gitlab.com/dashboard/snippets)** for your snippets on GitLab.com.
|
||||
- **Explore all public snippets**: In the top navigation bar of your GitLab
|
||||
instance, go to **More > Snippets** and select **Explore snippets** to view
|
||||
- **Explore all public snippets**: On the top bar of your GitLab
|
||||
instance, select **Menu > Snippets** and select **Explore snippets** to view
|
||||
[all public snippets](https://gitlab.com/explore/snippets).
|
||||
- **View a project's snippets**: In your project,
|
||||
go to **Snippets**.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module API
|
|||
requires :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma'
|
||||
requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
|
||||
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
|
||||
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'api'
|
||||
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api'
|
||||
end
|
||||
post ":id/invitations" do
|
||||
params[:source] = find_source(source_type, params[:id])
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ module API
|
|||
requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)'
|
||||
requires :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.'
|
||||
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
|
||||
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'api'
|
||||
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api'
|
||||
end
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
post ":id/members" do
|
||||
|
|
|
|||
|
|
@ -40,24 +40,24 @@ module Gitlab
|
|||
TRANSLATION_LEVELS = {
|
||||
'bg' => 1,
|
||||
'cs_CZ' => 1,
|
||||
'de' => 19,
|
||||
'de' => 18,
|
||||
'en' => 100,
|
||||
'eo' => 1,
|
||||
'es' => 41,
|
||||
'es' => 40,
|
||||
'fil_PH' => 1,
|
||||
'fr' => 14,
|
||||
'fr' => 13,
|
||||
'gl_ES' => 1,
|
||||
'id_ID' => 0,
|
||||
'it' => 2,
|
||||
'ja' => 45,
|
||||
'ko' => 14,
|
||||
'ja' => 44,
|
||||
'ko' => 13,
|
||||
'nl_NL' => 1,
|
||||
'pl_PL' => 1,
|
||||
'pt_BR' => 22,
|
||||
'ru' => 32,
|
||||
'pl_PL' => 3,
|
||||
'pt_BR' => 21,
|
||||
'ru' => 30,
|
||||
'tr_TR' => 17,
|
||||
'uk' => 43,
|
||||
'zh_CN' => 72,
|
||||
'uk' => 42,
|
||||
'zh_CN' => 69,
|
||||
'zh_HK' => 3,
|
||||
'zh_TW' => 4
|
||||
}.freeze
|
||||
|
|
|
|||
|
|
@ -3750,9 +3750,6 @@ msgstr ""
|
|||
msgid "An error occurred while updating the configuration."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while updating the milestone."
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while updating the notification settings. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13063,9 +13060,15 @@ msgstr ""
|
|||
msgid "EscalationPolicies|Add escalation policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Add policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Create an escalation policy in GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Delete escalation policy"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Edit escalation policy"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13075,12 +13078,18 @@ msgstr ""
|
|||
msgid "EscalationPolicies|Email on-call user in schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Escalation policies"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Escalation rules"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|Failed to load oncall-schedules"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{schedule}"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -13096,6 +13105,9 @@ msgstr ""
|
|||
msgid "EscalationPolicies|THEN %{doAction} %{schedule}"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|mins"
|
||||
msgstr ""
|
||||
|
||||
msgid "Estimate"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -21249,9 +21261,6 @@ msgstr ""
|
|||
msgid "Milestones|Milestone %{milestoneTitle} was not found"
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestones|No milestones found"
|
||||
msgstr ""
|
||||
|
||||
msgid "Milestones|Ongoing Issues (open and assigned)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -31641,7 +31650,7 @@ msgstr ""
|
|||
msgid "Survey Response"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch Branch"
|
||||
msgid "Switch branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch branch/tag"
|
||||
|
|
|
|||
|
|
@ -308,6 +308,21 @@ RSpec.describe 'Admin::Users' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'user group count', :js do
|
||||
before do
|
||||
group = create(:group)
|
||||
group.add_developer(current_user)
|
||||
project = create(:project, group: create(:group))
|
||||
project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it 'displays count of the users authorized groups' do
|
||||
wait_for_requests
|
||||
|
||||
expect(page.find("[data-testid='user-group-count-#{current_user.id}']").text).to eq("2")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /admin/users/new' do
|
||||
|
|
@ -557,32 +572,6 @@ RSpec.describe 'Admin::Users' do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: Move to main GET /admin/users block once feature flag is removed. Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/290737
|
||||
context 'with vue_admin_users feature flag enabled', :js do
|
||||
before do
|
||||
stub_feature_flags(vue_admin_users: true)
|
||||
end
|
||||
|
||||
describe 'GET /admin/users' do
|
||||
context 'user group count', :js do
|
||||
before do
|
||||
group = create(:group)
|
||||
group.add_developer(current_user)
|
||||
project = create(:project, group: create(:group))
|
||||
project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it 'displays count of the users authorized groups' do
|
||||
visit admin_users_path
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page.find("[data-testid='user-group-count-#{current_user.id}']").text).to eq("2")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def click_user_dropdown_toggle(user_id)
|
||||
page.within("[data-testid='user-actions-#{user_id}']") do
|
||||
find("[data-testid='dropdown-toggle']").click
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ RSpec.describe 'Groups > Members > Manage members' do
|
|||
expect_snowplow_event(
|
||||
category: 'Members::CreateService',
|
||||
action: 'create_member',
|
||||
label: 'unknown',
|
||||
label: 'group-members-page',
|
||||
property: 'existing_user',
|
||||
user: user1
|
||||
)
|
||||
|
|
@ -189,7 +189,7 @@ RSpec.describe 'Groups > Members > Manage members' do
|
|||
expect_snowplow_event(
|
||||
category: 'Members::InviteService',
|
||||
action: 'create_member',
|
||||
label: 'unknown',
|
||||
label: 'group-members-page',
|
||||
property: 'net_new_user',
|
||||
user: user1
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ RSpec.describe 'Project members list', :js do
|
|||
expect_snowplow_event(
|
||||
category: 'Members::CreateService',
|
||||
action: 'create_member',
|
||||
label: 'unknown',
|
||||
label: 'project-members-page',
|
||||
property: 'existing_user',
|
||||
user: user1
|
||||
)
|
||||
|
|
@ -117,7 +117,7 @@ RSpec.describe 'Project members list', :js do
|
|||
expect_snowplow_event(
|
||||
category: 'Members::InviteService',
|
||||
action: 'create_member',
|
||||
label: 'unknown',
|
||||
label: 'project-members-page',
|
||||
property: 'net_new_user',
|
||||
user: user1
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,176 +0,0 @@
|
|||
import { GlLoadingIcon, GlDropdown } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data';
|
||||
import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
|
||||
import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
|
||||
import { createStore } from '~/boards/stores';
|
||||
|
||||
const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, referencePath: 'h/b#2' };
|
||||
|
||||
describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => {
|
||||
let wrapper;
|
||||
let store;
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
store = null;
|
||||
wrapper = null;
|
||||
});
|
||||
|
||||
const createWrapper = ({ milestone = null, loading = false } = {}) => {
|
||||
store = createStore();
|
||||
store.state.boardItems = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } };
|
||||
store.state.activeId = TEST_ISSUE.id;
|
||||
|
||||
wrapper = shallowMount(BoardSidebarMilestoneSelect, {
|
||||
store,
|
||||
provide: {
|
||||
canUpdate: true,
|
||||
},
|
||||
data: () => ({
|
||||
milestones: [TEST_MILESTONE],
|
||||
}),
|
||||
stubs: {
|
||||
'board-editable-item': BoardEditableItem,
|
||||
},
|
||||
mocks: {
|
||||
$apollo: {
|
||||
loading,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findCollapsed = () => wrapper.find('[data-testid="collapsed-content"]');
|
||||
const findLoader = () => wrapper.find(GlLoadingIcon);
|
||||
const findDropdown = () => wrapper.find(GlDropdown);
|
||||
const findBoardEditableItem = () => wrapper.find(BoardEditableItem);
|
||||
const findDropdownItem = () => wrapper.find('[data-testid="milestone-item"]');
|
||||
const findUnsetMilestoneItem = () => wrapper.find('[data-testid="no-milestone-item"]');
|
||||
const findNoMilestonesFoundItem = () => wrapper.find('[data-testid="no-milestones-found"]');
|
||||
|
||||
describe('when not editing', () => {
|
||||
it('opens the milestone dropdown on clicking edit', async () => {
|
||||
createWrapper();
|
||||
wrapper.vm.$refs.dropdown.show = jest.fn();
|
||||
|
||||
await findBoardEditableItem().vm.$emit('open');
|
||||
|
||||
expect(wrapper.vm.$refs.dropdown.show).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when editing', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
jest.spyOn(wrapper.vm.$refs.sidebarItem, 'collapse');
|
||||
});
|
||||
|
||||
it('collapses BoardEditableItem on clicking edit', async () => {
|
||||
await findBoardEditableItem().vm.$emit('close');
|
||||
|
||||
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('collapses BoardEditableItem on hiding dropdown', async () => {
|
||||
await findDropdown().vm.$emit('hide');
|
||||
|
||||
expect(wrapper.vm.$refs.sidebarItem.collapse).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders "None" when no milestone is selected', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(findCollapsed().text()).toBe('None');
|
||||
});
|
||||
|
||||
it('renders milestone title when set', () => {
|
||||
createWrapper({ milestone: TEST_MILESTONE });
|
||||
|
||||
expect(findCollapsed().text()).toContain(TEST_MILESTONE.title);
|
||||
});
|
||||
|
||||
it('shows loader while Apollo is loading', async () => {
|
||||
createWrapper({ milestone: TEST_MILESTONE, loading: true });
|
||||
|
||||
expect(findLoader().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('shows message when error or no milestones found', async () => {
|
||||
createWrapper();
|
||||
|
||||
await wrapper.setData({ milestones: [] });
|
||||
|
||||
expect(findNoMilestonesFoundItem().text()).toBe('No milestones found');
|
||||
});
|
||||
|
||||
describe('when milestone is selected', () => {
|
||||
beforeEach(async () => {
|
||||
createWrapper();
|
||||
|
||||
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
|
||||
store.state.boardItems[TEST_ISSUE.id].milestone = TEST_MILESTONE;
|
||||
});
|
||||
findDropdownItem().vm.$emit('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('collapses sidebar and renders selected milestone', () => {
|
||||
expect(findCollapsed().isVisible()).toBe(true);
|
||||
expect(findCollapsed().text()).toContain(TEST_MILESTONE.title);
|
||||
});
|
||||
|
||||
it('commits change to the server', () => {
|
||||
expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({
|
||||
milestoneId: TEST_MILESTONE.id,
|
||||
projectPath: 'h/b',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when milestone is set to "None"', () => {
|
||||
beforeEach(async () => {
|
||||
createWrapper({ milestone: TEST_MILESTONE });
|
||||
|
||||
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
|
||||
store.state.boardItems[TEST_ISSUE.id].milestone = null;
|
||||
});
|
||||
findUnsetMilestoneItem().vm.$emit('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('collapses sidebar and renders "None"', () => {
|
||||
expect(findCollapsed().isVisible()).toBe(true);
|
||||
expect(findCollapsed().text()).toBe('None');
|
||||
});
|
||||
|
||||
it('commits change to the server', () => {
|
||||
expect(wrapper.vm.setActiveIssueMilestone).toHaveBeenCalledWith({
|
||||
milestoneId: null,
|
||||
projectPath: 'h/b',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the mutation fails', () => {
|
||||
const testMilestone = { id: '1', title: 'Former milestone' };
|
||||
|
||||
beforeEach(async () => {
|
||||
createWrapper({ milestone: testMilestone });
|
||||
|
||||
jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
|
||||
throw new Error(['failed mutation']);
|
||||
});
|
||||
jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
|
||||
findDropdownItem().vm.$emit('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
});
|
||||
|
||||
it('collapses sidebar and renders former milestone', () => {
|
||||
expect(findCollapsed().isVisible()).toBe(true);
|
||||
expect(findCollapsed().text()).toContain(testMilestone.title);
|
||||
expect(wrapper.vm.setError).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -30,7 +30,6 @@ import {
|
|||
mockIssue2,
|
||||
rawIssue,
|
||||
mockIssues,
|
||||
mockMilestone,
|
||||
labels,
|
||||
mockActiveIssue,
|
||||
mockGroupProjects,
|
||||
|
|
@ -1495,60 +1494,6 @@ describe('setActiveItemSubscribed', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('setActiveIssueMilestone', () => {
|
||||
const state = { boardItems: { [mockIssue.id]: mockIssue } };
|
||||
const getters = { activeBoardItem: mockIssue };
|
||||
const testMilestone = {
|
||||
...mockMilestone,
|
||||
id: 'gid://gitlab/Milestone/1',
|
||||
};
|
||||
const input = {
|
||||
milestoneId: testMilestone.id,
|
||||
projectPath: 'h/b',
|
||||
};
|
||||
|
||||
it('should commit milestone after setting the issue', (done) => {
|
||||
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
|
||||
data: {
|
||||
updateIssue: {
|
||||
issue: {
|
||||
milestone: testMilestone,
|
||||
},
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const payload = {
|
||||
itemId: getters.activeBoardItem.id,
|
||||
prop: 'milestone',
|
||||
value: testMilestone,
|
||||
};
|
||||
|
||||
testAction(
|
||||
actions.setActiveIssueMilestone,
|
||||
input,
|
||||
{ ...state, ...getters },
|
||||
[
|
||||
{
|
||||
type: types.UPDATE_BOARD_ITEM_BY_ID,
|
||||
payload,
|
||||
},
|
||||
],
|
||||
[],
|
||||
done,
|
||||
);
|
||||
});
|
||||
|
||||
it('throws error if fails', async () => {
|
||||
jest
|
||||
.spyOn(gqlClient, 'mutate')
|
||||
.mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } });
|
||||
|
||||
await expect(actions.setActiveIssueMilestone({ getters }, input)).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setActiveItemTitle', () => {
|
||||
const state = {
|
||||
boardItems: { [mockIssue.id]: mockIssue },
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import eventHub from '~/invite_members/event_hub';
|
|||
jest.mock('~/experimentation/experiment_tracking');
|
||||
|
||||
const displayText = 'Invite team members';
|
||||
const triggerSource = '_trigger_source_';
|
||||
|
||||
let wrapper;
|
||||
let triggerProps;
|
||||
let findButton;
|
||||
|
|
@ -26,7 +28,7 @@ const createComponent = (props = {}) => {
|
|||
};
|
||||
|
||||
describe.each(['button', 'anchor'])('with triggerElement as %s', (triggerElement) => {
|
||||
triggerProps = { triggerElement };
|
||||
triggerProps = { triggerElement, triggerSource };
|
||||
findButton = () => wrapper.findComponent(triggerComponent[triggerElement]);
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -48,22 +50,14 @@ describe.each(['button', 'anchor'])('with triggerElement as %s', (triggerElement
|
|||
spy = jest.spyOn(eventHub, '$emit');
|
||||
});
|
||||
|
||||
it('emits openModal from an unknown source', () => {
|
||||
createComponent();
|
||||
|
||||
findButton().vm.$emit('click');
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('openModal', { inviteeType: 'members', source: 'unknown' });
|
||||
});
|
||||
|
||||
it('emits openModal from a named source', () => {
|
||||
createComponent({ triggerSource: '_trigger_source_' });
|
||||
createComponent();
|
||||
|
||||
findButton().vm.$emit('click');
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('openModal', {
|
||||
inviteeType: 'members',
|
||||
source: '_trigger_source_',
|
||||
source: triggerSource,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,11 +4,16 @@ import SidebarInviteMembers from '~/sidebar/components/assignees/sidebar_invite_
|
|||
|
||||
describe('Sidebar invite members component', () => {
|
||||
let wrapper;
|
||||
const issuableType = 'issue';
|
||||
|
||||
const findDirectInviteLink = () => wrapper.findComponent(InviteMembersTrigger);
|
||||
|
||||
const createComponent = () => {
|
||||
wrapper = shallowMount(SidebarInviteMembers);
|
||||
wrapper = shallowMount(SidebarInviteMembers, {
|
||||
propsData: {
|
||||
issuableType,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -23,5 +28,9 @@ describe('Sidebar invite members component', () => {
|
|||
it('renders a direct link to project members path', () => {
|
||||
expect(findDirectInviteLink().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('has expected attributes on the trigger', () => {
|
||||
expect(findDirectInviteLink().props('triggerSource')).toBe('issue-assignee-dropdown');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ RSpec.describe 'Mailer retries' do
|
|||
descendants.each { |a| a.queue_adapter = :test }
|
||||
end
|
||||
|
||||
it 'sets retries for mailers to 3' do
|
||||
it 'sets retries for mailers to 3', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332645' do
|
||||
DeviseMailer.user_admin_approval(create(:user)).deliver_later
|
||||
|
||||
expect(Sidekiq::Queues['mailers'].first).to include('retry' => 3)
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
||||
RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_gitlab_redis_trace_chunks do
|
||||
include ExclusiveLeaseHelpers
|
||||
|
||||
let_it_be(:build) { create(:ci_build, :running) }
|
||||
|
||||
let(:chunk_index) { 0 }
|
||||
let(:data_store) { :redis }
|
||||
let(:data_store) { :redis_trace_chunks }
|
||||
let(:raw_data) { nil }
|
||||
|
||||
let(:build_trace_chunk) do
|
||||
|
|
@ -22,6 +22,13 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
stub_artifacts_object_storage
|
||||
end
|
||||
|
||||
def redis_instance
|
||||
{
|
||||
redis: Gitlab::Redis::SharedState,
|
||||
redis_trace_chunks: Gitlab::Redis::TraceChunks
|
||||
}[data_store]
|
||||
end
|
||||
|
||||
describe 'chunk creation' do
|
||||
let(:metrics) { spy('metrics') }
|
||||
|
||||
|
|
@ -85,7 +92,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
|
||||
def external_data_counter
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis_instance.with do |redis|
|
||||
redis.scan_each(match: "gitlab:ci:trace:*:chunks:*").to_a.size
|
||||
end
|
||||
end
|
||||
|
|
@ -101,24 +108,16 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
subject { described_class.all_stores }
|
||||
|
||||
it 'returns a correctly ordered array' do
|
||||
is_expected.to eq(%i[redis database fog])
|
||||
end
|
||||
|
||||
it 'returns redis store as the lowest precedence' do
|
||||
expect(subject.first).to eq(:redis)
|
||||
end
|
||||
|
||||
it 'returns fog store as the highest precedence' do
|
||||
expect(subject.last).to eq(:fog)
|
||||
is_expected.to eq(%i[redis database fog redis_trace_chunks])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
subject { build_trace_chunk.data }
|
||||
|
||||
context 'when data_store is redis' do
|
||||
let(:data_store) { :redis }
|
||||
where(:data_store) { %i[redis redis_trace_chunks] }
|
||||
|
||||
with_them do
|
||||
before do
|
||||
build_trace_chunk.send(:unsafe_set_data!, +'Sample data in redis')
|
||||
end
|
||||
|
|
@ -148,6 +147,22 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#data_store' do
|
||||
subject { described_class.new.data_store }
|
||||
|
||||
context 'default value' do
|
||||
it { expect(subject).to eq('redis_trace_chunks') }
|
||||
|
||||
context 'when dedicated_redis_trace_chunks is disabled' do
|
||||
before do
|
||||
stub_feature_flags(dedicated_redis_trace_chunks: false)
|
||||
end
|
||||
|
||||
it { expect(subject).to eq('redis') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_store_class' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
|
|
@ -155,6 +170,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
:redis | Ci::BuildTraceChunks::Redis
|
||||
:database | Ci::BuildTraceChunks::Database
|
||||
:fog | Ci::BuildTraceChunks::Fog
|
||||
:redis_trace_chunks | Ci::BuildTraceChunks::RedisTraceChunks
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
@ -302,9 +318,9 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when data_store is redis' do
|
||||
let(:data_store) { :redis }
|
||||
where(:data_store) { %i[redis redis_trace_chunks] }
|
||||
|
||||
with_them do
|
||||
context 'when there are no data' do
|
||||
let(:data) { +'' }
|
||||
|
||||
|
|
@ -441,8 +457,9 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when data_store is redis' do
|
||||
let(:data_store) { :redis }
|
||||
where(:data_store) { %i[redis redis_trace_chunks] }
|
||||
|
||||
with_them do
|
||||
let(:data) { +'Sample data in redis' }
|
||||
|
||||
before do
|
||||
|
|
@ -475,9 +492,9 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
describe '#size' do
|
||||
subject { build_trace_chunk.size }
|
||||
|
||||
context 'when data_store is redis' do
|
||||
let(:data_store) { :redis }
|
||||
where(:data_store) { %i[redis redis_trace_chunks] }
|
||||
|
||||
with_them do
|
||||
context 'when data exists' do
|
||||
let(:data) { +'Sample data in redis' }
|
||||
|
||||
|
|
@ -537,9 +554,14 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
|
||||
subject { build_trace_chunk.persist_data! }
|
||||
|
||||
context 'when data_store is redis' do
|
||||
let(:data_store) { :redis }
|
||||
where(:data_store, :redis_class) do
|
||||
[
|
||||
[:redis, Ci::BuildTraceChunks::Redis],
|
||||
[:redis_trace_chunks, Ci::BuildTraceChunks::RedisTraceChunks]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
context 'when data exists' do
|
||||
before do
|
||||
build_trace_chunk.send(:unsafe_set_data!, data)
|
||||
|
|
@ -549,15 +571,15 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
let(:data) { +'a' * described_class::CHUNK_SIZE }
|
||||
|
||||
it 'persists the data' do
|
||||
expect(build_trace_chunk.redis?).to be_truthy
|
||||
expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
|
||||
expect(build_trace_chunk.data_store).to eq(data_store.to_s)
|
||||
expect(redis_class.new.data(build_trace_chunk)).to eq(data)
|
||||
expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
|
||||
expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to be_nil
|
||||
|
||||
subject
|
||||
|
||||
expect(build_trace_chunk.fog?).to be_truthy
|
||||
expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to be_nil
|
||||
expect(redis_class.new.data(build_trace_chunk)).to be_nil
|
||||
expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
|
||||
expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to eq(data)
|
||||
end
|
||||
|
|
@ -575,8 +597,8 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
it 'does not persist the data and the orignal data is intact' do
|
||||
expect { subject }.to raise_error(described_class::FailedToPersistDataError)
|
||||
|
||||
expect(build_trace_chunk.redis?).to be_truthy
|
||||
expect(Ci::BuildTraceChunks::Redis.new.data(build_trace_chunk)).to eq(data)
|
||||
expect(build_trace_chunk.data_store).to eq(data_store.to_s)
|
||||
expect(redis_class.new.data(build_trace_chunk)).to eq(data)
|
||||
expect(Ci::BuildTraceChunks::Database.new.data(build_trace_chunk)).to be_nil
|
||||
expect(Ci::BuildTraceChunks::Fog.new.data(build_trace_chunk)).to be_nil
|
||||
end
|
||||
|
|
@ -810,7 +832,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
|
||||
shared_examples_for 'deletes all build_trace_chunk and data in redis' do
|
||||
it 'deletes all build_trace_chunk and data in redis', :sidekiq_might_not_need_inline do
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis_instance.with do |redis|
|
||||
expect(redis.scan_each(match: "gitlab:ci:trace:*:chunks:*").to_a.size).to eq(3)
|
||||
end
|
||||
|
||||
|
|
@ -820,7 +842,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
|
||||
expect(described_class.count).to eq(0)
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
redis_instance.with do |redis|
|
||||
expect(redis.scan_each(match: "gitlab:ci:trace:*:chunks:*").to_a.size).to eq(0)
|
||||
end
|
||||
end
|
||||
|
|
@ -902,4 +924,38 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#live?' do
|
||||
subject { build_trace_chunk.live? }
|
||||
|
||||
where(:data_store, :value) do
|
||||
[
|
||||
[:redis, true],
|
||||
[:redis_trace_chunks, true],
|
||||
[:database, false],
|
||||
[:fog, false]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(value) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#flushed?' do
|
||||
subject { build_trace_chunk.flushed? }
|
||||
|
||||
where(:data_store, :value) do
|
||||
[
|
||||
[:redis, false],
|
||||
[:redis_trace_chunks, false],
|
||||
[:database, true],
|
||||
[:fog, true]
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(value) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,12 +5,6 @@ require 'spec_helper'
|
|||
RSpec.describe Ci::BuildTraceChunks::Database do
|
||||
let(:data_store) { described_class.new }
|
||||
|
||||
describe '#available?' do
|
||||
subject { data_store.available? }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
subject { data_store.data(model) }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,6 @@ require 'spec_helper'
|
|||
RSpec.describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do
|
||||
let(:data_store) { described_class.new }
|
||||
|
||||
describe '#available?' do
|
||||
subject { data_store.available? }
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
describe '#data' do
|
||||
subject { data_store.data(model) }
|
||||
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ RSpec.describe API::Invitations do
|
|||
expect_snowplow_event(
|
||||
category: 'Members::InviteService',
|
||||
action: 'create_member',
|
||||
label: 'api',
|
||||
label: 'invitations-api',
|
||||
property: 'net_new_user',
|
||||
user: maintainer
|
||||
)
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ RSpec.describe API::Members do
|
|||
expect_snowplow_event(
|
||||
category: 'Members::CreateService',
|
||||
action: 'create_member',
|
||||
label: 'api',
|
||||
label: 'members-api',
|
||||
property: 'existing_user',
|
||||
user: maintainer
|
||||
)
|
||||
|
|
@ -322,7 +322,7 @@ RSpec.describe API::Members do
|
|||
expect_snowplow_event(
|
||||
category: 'Members::CreateService',
|
||||
action: 'create_member',
|
||||
label: 'api',
|
||||
label: 'members-api',
|
||||
property: 'existing_user',
|
||||
user: maintainer
|
||||
)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ RSpec.describe Ci::AppendBuildTraceService do
|
|||
|
||||
expect(::Gitlab::ErrorTracking)
|
||||
.to receive(:log_exception)
|
||||
.with(anything, hash_including(chunk_index: 0, chunk_store: 'redis'))
|
||||
.with(anything, hash_including(chunk_index: 0, chunk_store: 'redis_trace_chunks'))
|
||||
|
||||
result = described_class
|
||||
.new(build, content_range: '0-128')
|
||||
|
|
|
|||
|
|
@ -30,4 +30,12 @@ RSpec.configure do |config|
|
|||
|
||||
redis_queues_cleanup!
|
||||
end
|
||||
|
||||
config.around(:each, :clean_gitlab_redis_trace_chunks) do |example|
|
||||
redis_trace_chunks_cleanup!
|
||||
|
||||
example.run
|
||||
|
||||
redis_trace_chunks_cleanup!
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,4 +17,9 @@ module RedisHelpers
|
|||
def redis_shared_state_cleanup!
|
||||
Gitlab::Redis::SharedState.with(&:flushall)
|
||||
end
|
||||
|
||||
# Usage: CI trace chunks
|
||||
def redis_trace_chunks_cleanup!
|
||||
Gitlab::Redis::TraceChunks.with(&:flushall)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue