Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-19 15:11:58 +00:00
parent 35d5ae4e3d
commit 4c083c8163
64 changed files with 675 additions and 212 deletions

View File

@ -233,7 +233,7 @@ gem 'js_regex', '~> 3.7'
gem 'device_detector'
# Redis
gem 'redis', '~> 4.4.0'
gem 'redis', '~> 4.7.0'
gem 'connection_pool', '~> 2.0'
# Redis session store

View File

@ -1098,7 +1098,7 @@ GEM
json
recursive-open-struct (1.1.3)
redcarpet (3.5.1)
redis (4.4.0)
redis (4.7.1)
redis-actionpack (5.3.0)
actionpack (>= 5, < 8)
redis-rack (>= 2.1.0, < 3)
@ -1701,7 +1701,7 @@ DEPENDENCIES
rdoc (~> 6.3.2)
re2 (~> 1.4.0)
recaptcha (~> 4.11)
redis (~> 4.4.0)
redis (~> 4.7.0)
redis-actionpack (~> 5.3.0)
redis-namespace (~> 1.8.1)
request_store (~> 1.5)

View File

@ -63,6 +63,7 @@ export default () => {
const isMarkdown = editBlobForm.data('is-markdown');
const previewMarkdownPath = editBlobForm.data('previewMarkdownPath');
const commitButton = $('.js-commit-button');
const commitButtonLoading = $('.js-commit-button-loading');
const cancelLink = $('#cancel-changes');
import('./edit_blob')
@ -88,6 +89,8 @@ export default () => {
});
commitButton.on('click', () => {
commitButton.addClass('gl-display-none');
commitButtonLoading.removeClass('gl-display-none');
window.onbeforeunload = null;
});

View File

@ -29,7 +29,7 @@ export default {
</script>
<template>
<div v-if="!calloutDismissed" class="pipeline-schedules-user-callout user-callout">
<div class="bordered-box landing content-block" data-testid="innerContent">
<div class="bordered-box landing content-block gl-p-5!" data-testid="innerContent">
<gl-button
category="tertiary"
icon="close"

View File

@ -282,7 +282,7 @@ export default {
const descriptions = {};
Object.entries(data).forEach(([key, { value, description }]) => {
if (description !== null) {
if (description) {
params[key] = value;
descriptions[key] = description;
}

View File

@ -232,7 +232,11 @@ export default {
</span>
</div>
<div class="issuable-info">
<work-item-type-icon v-if="showWorkItemTypeIcon" :work-item-type="issuable.type" />
<work-item-type-icon
v-if="showWorkItemTypeIcon"
:work-item-type="issuable.type"
show-tooltip-on-hover
/>
<slot v-if="hasSlotContents('reference')" name="reference"></slot>
<span v-else data-testid="issuable-reference" class="issuable-reference">
{{ reference }}

View File

@ -1,11 +1,14 @@
<script>
import { GlIcon } from '@gitlab/ui';
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { WORK_ITEMS_TYPE_MAP } from '../constants';
export default {
components: {
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
workItemType: {
type: String,
@ -22,6 +25,11 @@ export default {
required: false,
default: '',
},
showTooltipOnHover: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
iconName() {
@ -32,13 +40,21 @@ export default {
workItemTypeName() {
return WORK_ITEMS_TYPE_MAP[this.workItemType]?.name;
},
workItemTooltipTitle() {
return this.showTooltipOnHover ? this.workItemTypeName : '';
},
},
};
</script>
<template>
<span>
<gl-icon :name="iconName" class="gl-mr-2" />
<gl-icon
v-gl-tooltip.hover="showTooltipOnHover"
:name="iconName"
:title="workItemTooltipTitle"
class="gl-mr-2"
/>
<span v-if="workItemTypeName" :class="{ 'gl-sr-only': !showText }">{{ workItemTypeName }}</span>
</span>
</template>

View File

@ -28,13 +28,6 @@
.pipeline-schedule-table-row {
.branch-name-cell {
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.next-run-cell {
color: var(--gray-500, $gray-500);
}
a {
@ -50,7 +43,6 @@
.bordered-box.content-block {
border: 1px solid var(--border-color, $border-color);
background-color: transparent;
padding: $gl-spacing-scale-5;
}
}

View File

@ -459,7 +459,7 @@ a.gl-badge.badge-warning:active {
.gl-form-input:disabled,
.gl-form-input.form-control:disabled {
cursor: not-allowed;
color: #868686;
color: #999;
}
.gl-form-input::placeholder,
.gl-form-input.form-control::placeholder {

View File

@ -438,7 +438,7 @@ a.gl-badge.badge-warning:active {
.gl-form-input:disabled,
.gl-form-input.form-control:disabled {
cursor: not-allowed;
color: #868686;
color: #666;
}
.gl-form-input::placeholder,
.gl-form-input.form-control::placeholder {

View File

@ -245,7 +245,7 @@ fieldset:disabled a.btn {
.gl-form-input:disabled,
.gl-form-input.form-control:disabled {
cursor: not-allowed;
color: #868686;
color: #666;
}
.gl-form-input::placeholder,
.gl-form-input.form-control::placeholder {

View File

@ -0,0 +1,10 @@
%div{ formatted_options }
.row
.col-lg-4
%h4.gl-mt-0
= title
- if description?
%p
= description
.col-lg-8
= body

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Layouts
class HorizontalSectionComponent < ViewComponent::Base
# @param [Boolean] border
# @param [Hash] options
def initialize(border: true, options: {})
@border = border
@options = options
end
private
renders_one :title
renders_one :description
renders_one :body
def formatted_options
@options.merge({ class: [('gl-border-b' if @border), @options[:class]].flatten.compact })
end
end
end

View File

@ -112,7 +112,7 @@ module Pajamas
def base_attributes
attributes = {}
attributes['disabled'] = '' if @disabled || @loading
attributes['disabled'] = 'disabled' if @disabled || @loading
attributes['aria-disabled'] = true if @disabled || @loading
attributes['type'] = @type unless @href

View File

@ -83,21 +83,21 @@ class ActiveSession
is_impersonated: request.session[:impersonator_id].present?
)
redis.pipelined do
redis.setex(
redis.pipelined do |pipeline|
pipeline.setex(
key_name(user.id, session_private_id),
expiry,
active_user_session.dump
)
# Deprecated legacy format - temporary to support mixed deployments
redis.setex(
pipeline.setex(
key_name_v1(user.id, session_private_id),
expiry,
Marshal.dump(active_user_session)
)
redis.sadd(
pipeline.sadd(
lookup_key_name(user.id),
session_private_id
)

View File

@ -1,42 +1,34 @@
= gitlab_ui_form_for [:admin, @group] do |f|
= form_errors(@group)
.gl-border-b.gl-mb-6
.row
.col-lg-4
%h4.gl-mt-0
= _('Naming, visibility')
%p
= _('Update your group name, description, avatar, and visibility.')
= link_to _('Learn more about groups.'), help_page_path('user/group/index')
.col-lg-8
= render 'shared/groups/group_name_and_path_fields', f: f
= render 'shared/group_form_description', f: f
.form-group.gl-form-group{ role: 'group' }
= f.label :avatar, _("Group avatar"), class: 'gl-display-block col-form-label'
= render 'shared/choose_avatar_button', f: f
= render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
= render ::Layouts::HorizontalSectionComponent.new(options: { class: 'gl-mb-6' }) do |c|
= c.title { _('Naming, visibility') }
= c.description do
= _('Update your group name, description, avatar, and visibility.')
= link_to _('Learn more about groups.'), help_page_path('user/group/index')
= c.body do
= render 'shared/groups/group_name_and_path_fields', f: f
= render 'shared/group_form_description', f: f
.form-group.gl-form-group{ role: 'group' }
= f.label :avatar, _("Group avatar"), class: 'gl-display-block col-form-label'
= render 'shared/choose_avatar_button', f: f
= render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
.gl-border-b.gl-pb-3.gl-mb-6
.row
.col-lg-4
%h4.gl-mt-0
= _('Permissions and group features')
%p
= _('Configure advanced permissions, Large File Storage, two-factor authentication, and CI/CD settings.')
.col-lg-8
= render_if_exists 'shared/old_repository_size_limit_setting', form: f, type: :group
= render_if_exists 'admin/namespace_plan', f: f
.form-group.gl-form-group{ role: 'group' }
= render 'shared/allow_request_access', form: f
= render 'groups/group_admin_settings', f: f
= render_if_exists 'namespaces/shared_runners_minutes_settings', group: @group, form: f
.gl-mb-3
.row
.col-lg-4
%h4.gl-mt-0
= _('Admin notes')
.col-lg-8
= render 'shared/admin/admin_note_form', f: f
= render ::Layouts::HorizontalSectionComponent.new(options: { class: 'gl-pb-3 gl-mb-6' }) do |c|
= c.title { _('Permissions and group features') }
= c.description do
= _('Configure advanced permissions, Large File Storage, two-factor authentication, and CI/CD settings.')
= c.body do
= render_if_exists 'shared/old_repository_size_limit_setting', form: f, type: :group
= render_if_exists 'admin/namespace_plan', f: f
.form-group.gl-form-group{ role: 'group' }
= render 'shared/allow_request_access', form: f
= render 'groups/group_admin_settings', f: f
= render_if_exists 'namespaces/shared_runners_minutes_settings', group: @group, form: f
= render ::Layouts::HorizontalSectionComponent.new(border: false, options: { class: 'gl-pb-3' }) do |c|
= c.title { _('Admin notes') }
= c.body do
= render 'shared/admin/admin_note_form', f: f
- if @group.new_record?
= render Pajamas::AlertComponent.new(dismissible: false) do |c|

View File

@ -2,7 +2,7 @@
- if Feature.enabled?(:restyle_login_page, @project)
.gl-text-center.gl-pt-5
%label.gl-font-weight-normal
= _("Create an account using:")
= _("Register with:")
.gl-text-center.gl-w-90p.gl-ml-auto.gl-mr-auto
- providers.each do |provider|
= link_to omniauth_authorize_path(:user, provider, register_omniauth_params), method: :post, class: "btn gl-button btn-default gl-ml-2 gl-mr-2 gl-mb-2 js-oauth-login #{qa_class_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do

View File

@ -1,9 +1,17 @@
- return unless Gitlab::CurrentSettings.current_application_settings.enforce_terms?
%p.gl-text-gray-500.gl-mt-5.gl-mb-0
- if Gitlab.com?
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the GitLab %{link_start}Terms of Use and Privacy Policy%{link_end}")) % { button_text: button_text,
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
- if Feature.enabled?(:restyle_login_page, @project)
- if Gitlab.com?
= html_escape(s_("SignUp|By clicking %{button_text} or registering through a third party you accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}")) % { button_text: button_text,
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
- else
= html_escape(s_("SignUp|By clicking %{button_text} or registering through a third party you accept the%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}")) % { button_text: button_text,
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
- else
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the %{link_start}Terms of Use and Privacy Policy%{link_end}")) % { button_text: button_text,
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
- if Gitlab.com?
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the GitLab %{link_start}Terms of Use and Privacy Policy%{link_end}")) % { button_text: button_text,
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
- else
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the %{link_start}Terms of Use and Privacy Policy%{link_end}")) % { button_text: button_text,
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }

View File

@ -1,5 +1,8 @@
.form-actions.gl-display-flex
= render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { id: 'commit-changes', class: 'js-commit-button', data: { qa_selector: 'commit_button' } }) do
- submit_button_options = { type: :submit, variant: :confirm, button_options: { id: 'commit-changes', class: 'js-commit-button', data: { qa_selector: 'commit_button' } } }
= render Pajamas::ButtonComponent.new(**submit_button_options) do
= _('Commit changes')
= render Pajamas::ButtonComponent.new(loading: true, disabled: true, **submit_button_options.merge({ button_options: { class: 'js-commit-button-loading gl-display-none' } })) do
= _('Commit changes')
= render Pajamas::ButtonComponent.new(href: cancel_path, button_options: { class: 'gl-ml-3', id: 'cancel-changes', aria: { label: _('Discard changes') }, data: { confirm: leave_edit_message, confirm_btn_variant: "danger" } }) do

View File

@ -2,7 +2,7 @@
%tr.pipeline-schedule-table-row
%td
= pipeline_schedule.description
%td.branch-name-cell
%td.branch-name-cell.gl-text-truncate
- if pipeline_schedule.for_tag?
= sprite_icon('tag', size: 12)
- else
@ -17,7 +17,7 @@
%span ##{pipeline_schedule.last_pipeline.id}
- else
= s_("PipelineSchedules|None")
%td.next-run-cell
%td.gl-text-gray-500{ 'data-testid': 'next-run-cell' }
- if pipeline_schedule.active? && pipeline_schedule.next_run_at
= time_ago_with_tooltip(pipeline_schedule.real_next_run)
- else

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
Redis.raise_deprecations = true unless Rails.env.production?
# We set the instance variable directly to suppress warnings.
# We cannot switch to the new behavior until we change all existing `redis.exists` calls to `redis.exists?`.
# Some gems also need to be updated

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
class ScheduleRemoveSelfManagedWikiNotes < Gitlab::Database::Migration[2.0]
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = 'RemoveSelfManagedWikiNotes'
INTERVAL = 2.minutes
disable_ddl_transaction!
def up
return if skip_migration?
queue_batched_background_migration(
MIGRATION,
:notes,
:id,
job_interval: INTERVAL,
batch_size: 10_000,
sub_batch_size: 1_000
)
end
def down
return if skip_migration?
delete_batched_background_migration(MIGRATION, :notes, :id, [])
end
private
def skip_migration?
Gitlab.staging? || Gitlab.com?
end
end

View File

@ -0,0 +1 @@
9dc41d0d5f1c87f27327b254c955eada4fcc5c6158c513128e6fbdadd6c34932

View File

@ -1363,7 +1363,10 @@ It renders on the GitLab documentation site as:
## Tabs
Use tabs to show different options.
On the docs site, you can format text so it's displayed as tabs.
NOTE:
For now, tabs are for testing only. Do not use them on the production docs site.
To create a set of tabs, follow this example:
@ -1389,7 +1392,7 @@ The headings determine the tab titles. Each tab is populated with the content be
Use brief words for the titles, ensure they are parallel, and start each with a capital letter. For example:
- `Omnibus`, `Chart`, `Source`
- `Omnibus package`, `Helm chart`, `Source`
- `15.1 and earlier`, `15.2 and later`
See [Pajamas](https://design.gitlab.com/components/tabs/#guidelines) for details.

View File

@ -205,7 +205,34 @@ To actually initialize this component, make sure to call the `initToggle` helper
For the full list of options, see its
[source](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/components/pajamas/toggle_component.rb).
### Best practices
## Layouts
Layout components can be used to create common layout patterns used in GitLab.
### Available components
#### Horizontal section
Many of the settings pages use a layout where the title and description are on the left and the settings fields are on the right. The `Layouts::HorizontalSectionComponent` can be used to create this layout.
**Example:**
```haml
= render ::Layouts::HorizontalSectionComponent.new(options: { class: 'gl-mb-6' }) do |c|
= c.title { _('Naming, visibility') }
= c.description do
= _('Update your group name, description, avatar, and visibility.')
= link_to _('Learn more about groups.'), help_page_path('user/group/index')
= c.body do
.form-group.gl-form-group
= f.label :name, _('New group name')
= f.text_field :name
```
For the full list of options, see its
[source](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/components/layouts/horizontal_section_component.rb).
## Best practices
- If you are about to create a new view in Haml, use the available components
over creating plain Haml tags with CSS classes.

View File

@ -5,9 +5,9 @@ module Gitlab
class IncrementPerAction < BaseStrategy
def increment(cache_key, expiry)
with_redis do |redis|
redis.pipelined do
redis.incr(cache_key)
redis.expire(cache_key, expiry)
redis.pipelined do |pipeline|
pipeline.incr(cache_key)
pipeline.expire(cache_key, expiry)
end.first
end
end

View File

@ -9,10 +9,10 @@ module Gitlab
def increment(cache_key, expiry)
with_redis do |redis|
redis.pipelined do
redis.sadd(cache_key, resource_key)
redis.expire(cache_key, expiry)
redis.scard(cache_key)
redis.pipelined do |pipeline|
pipeline.sadd(cache_key, resource_key)
pipeline.expire(cache_key, expiry)
pipeline.scard(cache_key)
end.last
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Removes obsolete wiki notes
class RemoveSelfManagedWikiNotes < BatchedMigrationJob
def perform
each_sub_batch(
operation_name: :delete_all
) do |sub_batch|
sub_batch.where(noteable_type: 'Wiki').delete_all
end
end
end
end
end

View File

@ -153,11 +153,11 @@ module Gitlab
# timeout - The time after which the cache key should expire.
def self.write_multiple(mapping, key_prefix: nil, timeout: TIMEOUT)
Redis::Cache.with do |redis|
redis.pipelined do |multi|
redis.pipelined do |pipeline|
mapping.each do |raw_key, value|
key = cache_key_for("#{key_prefix}#{raw_key}")
multi.set(key, value, ex: timeout)
pipeline.set(key, value, ex: timeout)
end
end
end

View File

@ -48,14 +48,14 @@ module Gitlab
::Gitlab::Redis::Cache.with do |redis|
# we use a pipeline instead of a MSET because each tag has
# a specific ttl
redis.pipelined do
redis.pipelined do |pipeline|
cacheable_tags.each do |tag|
created_at = tag.created_at
# ttl is the max_ttl_in_seconds reduced by the number
# of seconds that the tag has already existed
ttl = max_ttl_in_seconds - (now - created_at).seconds
ttl = ttl.to_i
redis.set(cache_key(tag), created_at.rfc3339, ex: ttl) if ttl > 0
pipeline.set(cache_key(tag), created_at.rfc3339, ex: ttl) if ttl > 0
end
end
end

View File

@ -135,9 +135,9 @@ module Gitlab
#
def write_to_redis_hash(hash)
Gitlab::Redis::Cache.with do |redis|
redis.pipelined do
redis.pipelined do |pipeline|
hash.each do |diff_file_id, highlighted_diff_lines_hash|
redis.hset(
pipeline.hset(
key,
diff_file_id,
gzip_compress(highlighted_diff_lines_hash.to_json)
@ -147,7 +147,7 @@ module Gitlab
end
# HSETs have to have their expiration date manually updated
redis.expire(key, expiration)
pipeline.expire(key, expiration)
end
record_memory_usage(fetch_memory_usage(redis, key))
@ -202,9 +202,9 @@ module Gitlab
expiration_period = expiration
Gitlab::Redis::Cache.with do |redis|
redis.pipelined do
results = redis.hmget(cache_key, file_paths)
redis.expire(key, expiration_period) if highlight_diffs_renewable_expiration_enabled
redis.pipelined do |pipeline|
results = pipeline.hmget(cache_key, file_paths)
pipeline.expire(key, expiration_period) if highlight_diffs_renewable_expiration_enabled
end
end

View File

@ -16,9 +16,9 @@ module Gitlab
etags = keys.map { generate_etag }
Gitlab::Redis::SharedState.with do |redis|
redis.pipelined do
redis.pipelined do |pipeline|
keys.each_with_index do |key, i|
redis.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing)
pipeline.set(redis_shared_state_key(key), etags[i], ex: EXPIRY_TIME, nx: only_if_missing)
end
end
end

View File

@ -20,8 +20,8 @@ module Gitlab
def store(new_access, new_reason, new_refreshed_at)
::Gitlab::Redis::Cache.with do |redis|
redis.pipelined do
redis.mapped_hmset(
redis.pipelined do |pipeline|
pipeline.mapped_hmset(
cache_key,
{
access: new_access.to_s,
@ -30,7 +30,7 @@ module Gitlab
}
)
redis.expire(cache_key, VALIDITY_TIME)
pipeline.expire(cache_key, VALIDITY_TIME)
end
end
end

View File

@ -14,9 +14,9 @@ module Gitlab
def save(repositories, group_id)
Gitlab::Redis::SharedState.with do |redis|
redis.multi do
redis.set(key_for('repositories'), Gitlab::Json.dump(repositories), ex: EXPIRY_TIME)
redis.set(key_for('group_id'), group_id, ex: EXPIRY_TIME)
redis.multi do |multi|
multi.set(key_for('repositories'), Gitlab::Json.dump(repositories), ex: EXPIRY_TIME)
multi.set(key_for('group_id'), group_id, ex: EXPIRY_TIME)
end
end
end

View File

@ -10,9 +10,9 @@ module Gitlab
results = {}
Gitlab::Redis::Cache.with do |r|
r.pipelined do
r.pipelined do |pipeline|
subjects.each do |subject|
results[subject.cache_key] = new(subject).read
results[subject.cache_key] = new(subject).read(pipeline)
end
end
end
@ -34,11 +34,15 @@ module Gitlab
end
end
def read
def read(pipeline = nil)
@loaded = true
Gitlab::Redis::Cache.with do |r|
r.mapped_hmget(markdown_cache_key, *fields)
if pipeline
pipeline.mapped_hmget(markdown_cache_key, *fields)
else
Gitlab::Redis::Cache.with do |r|
r.mapped_hmget(markdown_cache_key, *fields)
end
end
end

View File

@ -15,8 +15,8 @@ module Gitlab
keys = read(key).map { |value| "#{cache_namespace}:#{value}" }
keys << cache_key(key)
redis.pipelined do
keys.each_slice(1000) { |subset| redis.unlink(*subset) }
redis.pipelined do |pipeline|
keys.each_slice(1000) { |subset| pipeline.unlink(*subset) }
end
end
end

View File

@ -267,7 +267,7 @@ module Gitlab
def same_redis_store?
strong_memoize(:same_redis_store) do
# <Redis client v4.4.0 for redis:///path_to/redis/redis.socket/5>"
# <Redis client v4.7.1 for unix:///path_to/redis/redis.socket/5>"
primary_store.inspect == secondary_store.inspect
end
end

View File

@ -83,14 +83,14 @@ module Gitlab
full_key = cache_key(key)
with do |redis|
results = redis.pipelined do
results = redis.pipelined do |pipeline|
# Set each hash key to the provided value
hash.each do |h_key, h_value|
redis.hset(full_key, h_key, h_value)
pipeline.hset(full_key, h_key, h_value)
end
# Update the expiry time for this hset
redis.expire(full_key, expires_in)
pipeline.expire(full_key, expires_in)
end
results.all?

View File

@ -21,14 +21,14 @@ module Gitlab
full_key = cache_key(key)
with do |redis|
redis.multi do
redis.unlink(full_key)
redis.multi do |multi|
multi.unlink(full_key)
# Splitting into groups of 1000 prevents us from creating a too-long
# Redis command
value.each_slice(1000) { |subset| redis.sadd(full_key, subset) }
value.each_slice(1000) { |subset| multi.sadd(full_key, subset) }
redis.expire(full_key, expires_in)
multi.expire(full_key, expires_in)
end
end
@ -39,9 +39,9 @@ module Gitlab
full_key = cache_key(key)
smembers, exists = with do |redis|
redis.multi do
redis.smembers(full_key)
redis.exists(full_key)
redis.multi do |multi|
multi.smembers(full_key)
multi.exists(full_key)
end
end

View File

@ -33,10 +33,10 @@ module Gitlab
def write(key, value)
with do |redis|
redis.pipelined do
redis.sadd(cache_key(key), value)
redis.pipelined do |pipeline|
pipeline.sadd(cache_key(key), value)
redis.expire(cache_key(key), expires_in)
pipeline.expire(cache_key(key), expires_in)
end
end
@ -57,9 +57,9 @@ module Gitlab
full_key = cache_key(key)
with do |redis|
redis.multi do
redis.sismember(full_key, value)
redis.exists(full_key)
redis.multi do |multi|
multi.sismember(full_key, value)
multi.exists(full_key)
end
end
end

View File

@ -40,9 +40,9 @@ module Gitlab
cache_key = cache_key_for_hook(hook)
::Gitlab::Redis::SharedState.with do |redis|
redis.multi do
redis.sadd(cache_key, hook.id)
redis.expire(cache_key, TOUCH_CACHE_TTL)
redis.multi do |multi|
multi.sadd(cache_key, hook.id)
multi.expire(cache_key, TOUCH_CACHE_TTL)
end
end
end

View File

@ -32349,6 +32349,9 @@ msgstr ""
msgid "Register with two-factor app"
msgstr ""
msgid "Register with:"
msgstr ""
msgid "RegistrationFeatures|Enable Service Ping and register for this feature."
msgstr ""
@ -36563,6 +36566,12 @@ msgstr ""
msgid "Sign-up restrictions"
msgstr ""
msgid "SignUp|By clicking %{button_text} or registering through a third party you accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}"
msgstr ""
msgid "SignUp|By clicking %{button_text} or registering through a third party you accept the%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}"
msgstr ""
msgid "SignUp|By clicking %{button_text}, I agree that I have read and accepted the %{link_start}Terms of Use and Privacy Policy%{link_end}"
msgstr ""

View File

@ -52,7 +52,7 @@
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "3.1.0",
"@gitlab/ui": "43.6.0",
"@gitlab/ui": "43.7.1",
"@gitlab/visual-review-tools": "1.7.3",
"@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7",

View File

@ -0,0 +1,88 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Layouts::HorizontalSectionComponent, type: :component do
let(:title) { 'Naming, visibility' }
let(:description) { 'Update your group name, description, avatar, and visibility.' }
let(:body) { 'This is where the settings go' }
describe 'slots' do
it 'renders title' do
render_inline described_class.new do |c|
c.title { title }
c.body { body }
end
expect(page).to have_css('h4', text: title)
end
it 'renders body slot' do
render_inline described_class.new do |c|
c.title { title }
c.body { body }
end
expect(page).to have_content(body)
end
context 'when description slot is provided' do
before do
render_inline described_class.new do |c|
c.title { title }
c.description { description }
c.body { body }
end
end
it 'renders description' do
expect(page).to have_css('p', text: description)
end
end
context 'when description slot is not provided' do
before do
render_inline described_class.new do |c|
c.title { title }
c.body { body }
end
end
it 'does not render description' do
expect(page).not_to have_css('p', text: description)
end
end
end
describe 'arguments' do
describe 'border' do
it 'defaults to true and adds gl-border-b CSS class' do
render_inline described_class.new do |c|
c.title { title }
c.body { body }
end
expect(page).to have_css('.gl-border-b')
end
it 'does not add gl-border-b CSS class when set to false' do
render_inline described_class.new(border: false) do |c|
c.title { title }
c.body { body }
end
expect(page).not_to have_css('.gl-border-b')
end
end
describe 'options' do
it 'adds options to wrapping element' do
render_inline described_class.new(options: { data: { testid: 'foo-bar' }, class: 'foo-bar' }) do |c|
c.title { title }
c.body { body }
end
expect(page).to have_css('.foo-bar[data-testid="foo-bar"]')
end
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Layouts
class HorizontalSectionComponentPreview < ViewComponent::Preview
# @param border toggle
# @param title text
# @param description text
# @param body text
def default(
border: true,
title: 'Naming, visibility',
description: 'Update your group name, description, avatar, and visibility.',
body: 'Settings fields here.'
)
render(::Layouts::HorizontalSectionComponent.new(border: border, options: { class: 'gl-mb-6 gl-pb-3' })) do |c|
c.title { title }
c.description { description }
c.body { body }
end
end
end
end

View File

@ -102,6 +102,21 @@ RSpec.describe 'Projects > Files > User edits files', :js do
expect(page).to have_content('*.rbca')
end
it 'shows loader on commit changes' do
set_default_button('edit')
click_link('.gitignore')
click_link_or_button('Edit')
# why: We don't want the form to actually submit, so that we can assert the button's changed state
page.execute_script("document.querySelector('.js-edit-blob-form').addEventListener('submit', e => e.preventDefault())")
find('.file-editor', match: :first)
editor_set_value('*.rbca')
click_button('Commit changes')
expect(page).to have_button('Commit changes', disabled: true, class: 'js-commit-button-loading')
end
it 'shows the diff of an edited file' do
set_default_button('edit')
click_link('.gitignore')

View File

@ -95,7 +95,7 @@ RSpec.describe 'Pipeline Schedules', :js do
it 'displays the required information description' do
page.within('.pipeline-schedule-table-row') do
expect(page).to have_content('pipeline schedule')
expect(find(".next-run-cell time")['title'])
expect(find("[data-testid='next-run-cell'] time")['title'])
.to include(pipeline_schedule.real_next_run.strftime('%b %-d, %Y'))
expect(page).to have_link('master')
expect(page).to have_link("##{pipeline.id}")
@ -259,7 +259,7 @@ RSpec.describe 'Pipeline Schedules', :js do
click_button 'Save pipeline schedule'
page.within('.pipeline-schedule-table-row:nth-child(1)') do
expect(page).to have_css(".next-run-cell time")
expect(page).to have_css("[data-testid='next-run-cell'] time")
end
end
end

View File

@ -329,6 +329,12 @@ describe('Pipeline New Form', () => {
value: mockYmlValue,
description: null,
},
yml_var2: {
value: 'yml_var2_val',
},
yml_var3: {
description: '',
},
});
await waitForPromises();

View File

@ -1,11 +1,17 @@
import { GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
let wrapper;
function createComponent(propsData) {
wrapper = shallowMount(WorkItemTypeIcon, { propsData });
wrapper = shallowMount(WorkItemTypeIcon, {
propsData,
directives: {
GlTooltip: createMockDirective(),
},
});
}
describe('Work Item type component', () => {
@ -16,22 +22,23 @@ describe('Work Item type component', () => {
});
describe.each`
workItemType | workItemIconName | iconName | text
${'TASK'} | ${''} | ${'issue-type-task'} | ${'Task'}
${''} | ${'issue-type-task'} | ${'issue-type-task'} | ${''}
${'ISSUE'} | ${''} | ${'issue-type-issue'} | ${'Issue'}
${''} | ${'issue-type-issue'} | ${'issue-type-issue'} | ${''}
${'REQUIREMENTS'} | ${''} | ${'issue-type-requirements'} | ${'Requirements'}
${'INCIDENT'} | ${''} | ${'issue-type-incident'} | ${'Incident'}
${'TEST_CASE'} | ${''} | ${'issue-type-test-case'} | ${'Test case'}
${'random-issue-type'} | ${''} | ${'issue-type-issue'} | ${''}
workItemType | workItemIconName | iconName | text | showTooltipOnHover
${'TASK'} | ${''} | ${'issue-type-task'} | ${'Task'} | ${false}
${''} | ${'issue-type-task'} | ${'issue-type-task'} | ${''} | ${true}
${'ISSUE'} | ${''} | ${'issue-type-issue'} | ${'Issue'} | ${true}
${''} | ${'issue-type-issue'} | ${'issue-type-issue'} | ${''} | ${true}
${'REQUIREMENTS'} | ${''} | ${'issue-type-requirements'} | ${'Requirements'} | ${true}
${'INCIDENT'} | ${''} | ${'issue-type-incident'} | ${'Incident'} | ${false}
${'TEST_CASE'} | ${''} | ${'issue-type-test-case'} | ${'Test case'} | ${true}
${'random-issue-type'} | ${''} | ${'issue-type-issue'} | ${''} | ${true}
`(
'with workItemType set to "$workItemType" and workItemIconName set to "$workItemIconName"',
({ workItemType, workItemIconName, iconName, text }) => {
({ workItemType, workItemIconName, iconName, text, showTooltipOnHover }) => {
beforeEach(() => {
createComponent({
workItemType,
workItemIconName,
showTooltipOnHover,
});
});
@ -42,6 +49,12 @@ describe('Work Item type component', () => {
it(`renders correct text`, () => {
expect(wrapper.text()).toBe(text);
});
it('shows tooltip on hover when props passed', () => {
const tooltip = getBinding(findIcon().element, 'gl-tooltip');
expect(tooltip.value).toBe(showTooltipOnHover);
});
},
);
});

View File

@ -22,7 +22,7 @@ RSpec.describe 'ActionCableSubscriptionAdapterIdentifier override' do
sub = ActionCable.server.pubsub.send(:redis_connection)
expect(sub.connection[:id]).to eq('redis:///home/localuser/redis/redis.socket/0')
expect(sub.connection[:id]).to eq('unix:///home/localuser/redis/redis.socket/0')
expect(ActionCable.server.config.cable[:id]).to be_nil
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::RemoveSelfManagedWikiNotes, :migration, schema: 20220601110011 do
let(:notes) { table(:notes) }
subject(:perform_migration) do
described_class.new(start_id: 1,
end_id: 30,
batch_table: :notes,
batch_column: :id,
sub_batch_size: 2,
pause_ms: 0,
connection: ActiveRecord::Base.connection)
.perform
end
it 'removes all wiki notes' do
notes.create!(id: 2, note: 'Commit note', noteable_type: 'Commit')
notes.create!(id: 10, note: 'Issue note', noteable_type: 'Issue')
notes.create!(id: 20, note: 'Wiki note', noteable_type: 'Wiki')
notes.create!(id: 30, note: 'MergeRequest note', noteable_type: 'MergeRequest')
expect(notes.where(noteable_type: 'Wiki').size).to eq(1)
expect { perform_migration }.to change(notes, :count).by(-1)
expect(notes.where(noteable_type: 'Wiki').size).to eq(0)
end
end

View File

@ -79,10 +79,14 @@ RSpec.describe ::Gitlab::ContainerRepository::Tags::Cache, :clean_gitlab_redis_c
it 'inserts values in redis' do
::Gitlab::Redis::Cache.with do |redis|
expect(redis)
.to receive(:set)
.with(cache_key(tag), rfc3339(tag.created_at), ex: ttl.to_i)
.and_call_original
expect(redis).to receive(:pipelined).and_call_original
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline)
.to receive(:set)
.with(cache_key(tag), rfc3339(tag.created_at), ex: ttl.to_i)
.and_call_original
end
end
subject

View File

@ -132,20 +132,12 @@ RSpec.describe Gitlab::Diff::HighlightCache, :clean_gitlab_redis_cache do
.once
.and_call_original
Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:expire).with(cache.key, expiration_period).at_least(:once)
end
2.times { cache.write_if_empty }
end
it 'reads from cache once' do
expect(cache).to receive(:read_cache).once.and_call_original
Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:expire).with(cache.key, expiration_period).at_least(:once)
end
cache.write_if_empty
end

View File

@ -62,7 +62,13 @@ RSpec.describe Gitlab::MarkdownCache::Redis::Extension, :clean_gitlab_redis_cach
it 'does not preload the markdown twice' do
expect(Gitlab::MarkdownCache::Redis::Store).to receive(:bulk_read).and_call_original
expect(Gitlab::Redis::Cache).to receive(:with).twice.and_call_original
Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:pipelined).and_call_original
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline).to receive(:mapped_hmget).once.and_call_original
end
end
klass.preload_markdown_cache!([thing])

View File

@ -46,7 +46,7 @@ RSpec.describe Gitlab::Redis::DuplicateJobs do
expect(redis_instance.primary_store.connection[:id]).to eq("redis://test-host:6379/99")
expect(redis_instance.primary_store.connection[:namespace]).to be_nil
expect(redis_instance.secondary_store.connection[:id]).to eq("redis:///path/to/redis.sock/0")
expect(redis_instance.secondary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
expect(redis_instance.secondary_store.connection[:namespace]).to eq("resque:gitlab")
expect(redis_instance.instance_name).to eq('DuplicateJobs')

View File

@ -264,13 +264,20 @@ RSpec.describe Gitlab::Redis::MultiStore do
context 'when the command is executed within pipelined block' do
subject do
multi_store.pipelined do
multi_store.send(name, *args)
multi_store.pipelined do |pipeline|
pipeline.send(name, *args)
end
end
it 'is executed only 1 time on primary instance' do
expect(primary_store).to receive(name).with(*args).once
it 'is executed only 1 time on primary and secondary instance' do
expect(primary_store).to receive(:pipelined).and_call_original
expect(secondary_store).to receive(:pipelined).and_call_original
2.times do
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline).to receive(name).with(*args).once.and_call_original
end
end
subject
end
@ -438,14 +445,21 @@ RSpec.describe Gitlab::Redis::MultiStore do
context 'when the command is executed within pipelined block' do
subject do
multi_store.pipelined do
multi_store.send(name, *args)
multi_store.pipelined do |pipeline|
pipeline.send(name, *args)
end
end
it 'is executed only 1 time on each instance', :aggregate_errors do
expect(primary_store).to receive(name).with(*expected_args).once
expect(secondary_store).to receive(name).with(*expected_args).once
expect(primary_store).to receive(:pipelined).and_call_original
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline).to receive(name).with(*expected_args).once.and_call_original
end
expect(secondary_store).to receive(:pipelined).and_call_original
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline).to receive(name).with(*expected_args).once.and_call_original
end
subject
end
@ -781,14 +795,20 @@ RSpec.describe Gitlab::Redis::MultiStore do
context 'when the command is executed within pipelined block' do
subject do
multi_store.pipelined do
multi_store.incr(key)
multi_store.pipelined do |pipeline|
pipeline.incr(key)
end
end
it 'is executed only 1 time on each instance', :aggregate_errors do
expect(primary_store).to receive(:incr).with(key).once
expect(secondary_store).to receive(:incr).with(key).once
expect(primary_store).to receive(:pipelined).once.and_call_original
expect(secondary_store).to receive(:pipelined).once.and_call_original
2.times do
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline).to receive(:incr).with(key).once
end
end
subject
end

View File

@ -42,7 +42,7 @@ RSpec.describe Gitlab::Redis::SidekiqStatus do
expect(redis_instance).to be_instance_of(::Gitlab::Redis::MultiStore)
expect(redis_instance.primary_store.connection[:id]).to eq("redis://test-host:6379/99")
expect(redis_instance.secondary_store.connection[:id]).to eq("redis:///path/to/redis.sock/0")
expect(redis_instance.secondary_store.connection[:id]).to eq("unix:///path/to/redis.sock/0")
expect(redis_instance.instance_name).to eq('SidekiqStatus')
end

View File

@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Sidebars::Projects::Menus::DeploymentsMenu do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:project, reload: true) { create(:project, :repository) }
let(:user) { project.first_owner }
let(:context) { Sidebars::Projects::Context.new(current_user: user, container: project) }
@ -37,6 +37,40 @@ RSpec.describe Sidebars::Projects::Menus::DeploymentsMenu do
specify { is_expected.to be_nil }
end
describe 'when the feature is disabled' do
before do
project.update_attribute("#{item_id}_access_level", 'disabled')
end
it { is_expected.to be_nil }
end
describe 'when split_operations_visibility_permissions FF is disabled' do
before do
stub_feature_flags(split_operations_visibility_permissions: false)
end
it { is_expected.not_to be_nil }
context 'and the feature is disabled' do
before do
project.update_attribute("#{item_id}_access_level", 'disabled')
end
it { is_expected.not_to be_nil }
end
context 'and operations is disabled' do
before do
project.update_attribute(:operations_access_level, 'disabled')
end
it do
is_expected.to be_nil if [:environments, :feature_flags].include?(item_id)
end
end
end
end
describe 'Feature Flags' do

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe ScheduleRemoveSelfManagedWikiNotes do
let_it_be(:batched_migration) { described_class::MIGRATION }
it 'schedules new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :notes,
column_name: :id,
interval: described_class::INTERVAL
)
}
end
end
context 'with com? or staging?' do
before do
allow(::Gitlab).to receive(:com?).and_return(true)
allow(::Gitlab).to receive(:staging?).and_return(false)
end
it 'does not schedule new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
end
end
end
end

View File

@ -395,11 +395,8 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
end
it 'caches the created_at values' do
::Gitlab::Redis::Cache.with do |redis|
expect_mget(redis, tags_and_created_ats.keys)
expect_set(redis, cacheable_tags)
end
expect_mget(tags_and_created_ats.keys)
expect_set(cacheable_tags)
expect(subject).to include(cached_tags_count: 0)
end
@ -412,12 +409,10 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
end
it 'uses them' do
::Gitlab::Redis::Cache.with do |redis|
expect_mget(redis, tags_and_created_ats.keys)
expect_mget(tags_and_created_ats.keys)
# because C is already in cache, it should not be cached again
expect_set(redis, cacheable_tags.except('C'))
end
# because C is already in cache, it should not be cached again
expect_set(cacheable_tags.except('C'))
# We will ping the container registry for all tags *except* for C because it's cached
expect(ContainerRegistry::Blob).to receive(:new).with(repository, { "digest" => "sha256:configA" }).and_call_original
@ -429,15 +424,27 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
end
end
def expect_mget(redis, keys)
expect(redis).to receive(:mget).with(keys.map(&method(:cache_key))).and_call_original
def expect_mget(keys)
Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:mget).with(keys.map(&method(:cache_key))).and_call_original
end
end
def expect_set(redis, tags)
tags.each do |tag_name, created_at|
def expect_set(tags)
selected_tags = tags.map do |tag_name, created_at|
ex = 1.day.seconds - (Time.zone.now - created_at).seconds
if ex > 0
expect(redis).to receive(:set).with(cache_key(tag_name), rfc3339(created_at), ex: ex.to_i)
[tag_name, created_at, ex.to_i] if ex.positive?
end.compact
return if selected_tags.count.zero?
Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:pipelined).and_call_original
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
selected_tags.each do |tag_name, created_at, ex|
expect(pipeline).to receive(:set).with(cache_key(tag_name), rfc3339(created_at), ex: ex)
end
end
end
end
@ -507,7 +514,11 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
def expect_caching
::Gitlab::Redis::Cache.with do |redis|
expect(redis).to receive(:mget).and_call_original
expect(redis).to receive(:set).and_call_original
expect(redis).to receive(:pipelined).and_call_original
expect_next_instance_of(Redis::PipelinedConnection) do |pipeline|
expect(pipeline).to receive(:set).and_call_original
end
end
end
end

View File

@ -120,7 +120,7 @@ module LoginHelpers
def register_via(provider, uid, email, additional_info: {})
mock_auth_hash(provider, uid, email, additional_info: additional_info)
visit new_user_registration_path
expect(page).to have_content('Create an account using')
expect(page).to have_content('Create an account using').or(have_content('Register with'))
click_link_or_button "oauth-login-#{provider}"
end

View File

@ -7,13 +7,15 @@ RSpec.describe 'devise/shared/_signup_box' do
let(:terms_path) { '_terms_path_' }
let(:translation_com) do
s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted "\
"the GitLab %{link_start}Terms of Use and Privacy Policy%{link_end}")
s_("SignUp|By clicking %{button_text} or registering through a third party you "\
"accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Policy "\
"and Cookie Policy%{link_end}")
end
let(:translation_non_com) do
s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted "\
"the %{link_start}Terms of Use and Privacy Policy%{link_end}")
s_("SignUp|By clicking %{button_text} or registering through a third party you "\
"accept the%{link_start} Terms of Use and acknowledge the Privacy Policy and "\
"Cookie Policy%{link_end}")
end
before do

View File

@ -38,6 +38,19 @@ var (
Help: "How many messages gitlab-workhorse has received in total on pubsub.",
},
)
totalActions = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "gitlab_workhorse_keywatcher_actions_total",
Help: "Counts of various keywatcher actions",
},
[]string{"action"},
)
receivedBytes = promauto.NewCounter(
prometheus.CounterOpts{
Name: "gitlab_workhorse_keywatcher_received_bytes_total",
Help: "How many bytes of messages gitlab-workhorse has received in total on pubsub.",
},
)
)
const (
@ -50,6 +63,8 @@ type KeyChan struct {
Chan chan string
}
func countAction(action string) { totalActions.WithLabelValues(action).Add(1) }
func processInner(conn redis.Conn) error {
defer conn.Close()
psc := redis.PubSubConn{Conn: conn}
@ -63,13 +78,13 @@ func processInner(conn redis.Conn) error {
case redis.Message:
totalMessages.Inc()
dataStr := string(v.Data)
receivedBytes.Add(float64(len(dataStr)))
msg := strings.SplitN(dataStr, "=", 2)
if len(msg) != 2 {
log.WithError(fmt.Errorf("keywatcher: invalid notification: %q", dataStr)).Error()
continue
}
key, value := msg[0], msg[1]
notifyChanWatchers(key, value)
notifyChanWatchers(msg[0], msg[1])
case error:
log.WithError(fmt.Errorf("keywatcher: pubsub receive: %v", v)).Error()
// Intermittent error, return nil so that it doesn't wait before reconnect
@ -131,37 +146,52 @@ func Shutdown() {
func notifyChanWatchers(key, value string) {
keyWatcherMutex.Lock()
defer keyWatcherMutex.Unlock()
if chanList, ok := keyWatcher[key]; ok {
for _, c := range chanList {
c <- value
keyWatchers.Dec()
}
delete(keyWatcher, key)
chanList, ok := keyWatcher[key]
if !ok {
countAction("drop-message")
return
}
countAction("deliver-message")
for _, c := range chanList {
c <- value
keyWatchers.Dec()
}
delete(keyWatcher, key)
}
func addKeyChan(kc *KeyChan) {
keyWatcherMutex.Lock()
defer keyWatcherMutex.Unlock()
keyWatcher[kc.Key] = append(keyWatcher[kc.Key], kc.Chan)
keyWatchers.Inc()
if len(keyWatcher[kc.Key]) == 1 {
countAction("create-subscription")
}
}
func delKeyChan(kc *KeyChan) {
keyWatcherMutex.Lock()
defer keyWatcherMutex.Unlock()
if chans, ok := keyWatcher[kc.Key]; ok {
for i, c := range chans {
if kc.Chan == c {
keyWatcher[kc.Key] = append(chans[:i], chans[i+1:]...)
keyWatchers.Dec()
break
}
}
if len(keyWatcher[kc.Key]) == 0 {
delete(keyWatcher, kc.Key)
chans, ok := keyWatcher[kc.Key]
if !ok {
return
}
for i, c := range chans {
if kc.Chan == c {
keyWatcher[kc.Key] = append(chans[:i], chans[i+1:]...)
keyWatchers.Dec()
break
}
}
if len(keyWatcher[kc.Key]) == 0 {
delete(keyWatcher, kc.Key)
countAction("delete-subscription")
}
}
// WatchKeyStatus is used to tell how WatchKey returned
@ -211,7 +241,6 @@ func WatchKey(key, value string, timeout time.Duration) (WatchKeyStatus, error)
return WatchKeyStatusNoChange, nil
}
return WatchKeyStatusSeenChange, nil
case <-time.After(timeout):
return WatchKeyStatusTimeout, nil
}

View File

@ -1056,10 +1056,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.1.0.tgz#0108498a17e2f79d16158015db0be764b406cc09"
integrity sha512-kZ45VTQOgLdwQCLRSj7+aohF+6AUnAaoucR1CFY/6DPDLnNNGeflwsCLN0sFBKwx42HLxFfNwvDmKOMLdSQg5A==
"@gitlab/ui@43.6.0":
version "43.6.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.6.0.tgz#95f85f405455aa95ee5b68b5072bcbe1bd22f375"
integrity sha512-d0ebKUU7BBBCVoblEA6iWbD3BfR4t0YsDbC7UlN7nyR++MhojCmaML+L9MuDeJGpd0DWR0HLxAGWawtcdeZErw==
"@gitlab/ui@43.7.1":
version "43.7.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-43.7.1.tgz#0550d08ed3312650eb08df9294f25022fe574c64"
integrity sha512-L7Rf+Y2YcsnYFVf95m+8Q2EHXYRh2mbxYCu5S94xoIVUYEBGtiZW7uRsjeEuXZjo1yD54K7e3x9BzBem1onrZw==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"