Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-12-08 15:13:43 +00:00
parent 886ecba0bd
commit 26b7ec2c6a
51 changed files with 559 additions and 386 deletions

View File

@ -12,11 +12,20 @@ export default {
ModalCopyButton,
},
inject: ['defaultBranchName'],
model: {
prop: 'visible',
event: 'change',
},
props: {
modalId: {
type: String,
required: true,
},
visible: {
type: Boolean,
required: false,
default: false,
},
},
instructionText: {
step1: s__(
@ -57,12 +66,15 @@ export default {
</script>
<template>
<gl-modal
:visible="visible"
:modal-id="modalId"
:title="$options.modalInfo.title"
static
size="lg"
ok-only
ok-variant="light"
:ok-title="$options.modalInfo.closeText"
@change="$emit('change', $event)"
>
<p>
<gl-sprintf :message="$options.instructionText.step1">

View File

@ -1,12 +1,15 @@
<script>
import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
import { s__ } from '~/locale';
import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue';
export default {
components: {
EnvironmentFolder,
EnableReviewAppModal,
GlBadge,
GlTab,
GlTabs,
@ -22,22 +25,73 @@ export default {
query: pollIntervalQuery,
},
},
inject: ['newEnvironmentPath', 'canCreateEnvironment'],
i18n: {
newEnvironmentButtonLabel: s__('Environments|New environment'),
reviewAppButtonLabel: s__('Environments|Enable review app'),
},
modalId: 'enable-review-app-info',
data() {
return { interval: undefined };
return { interval: undefined, isReviewAppModalVisible: false };
},
computed: {
canSetupReviewApp() {
return this.environmentApp?.reviewApp?.canSetupReviewApp;
},
folders() {
return this.environmentApp?.environments.filter((e) => e.size > 1) ?? [];
},
availableCount() {
return this.environmentApp?.availableCount;
},
addEnvironment() {
if (!this.canCreateEnvironment) {
return null;
}
return {
text: this.$options.i18n.newEnvironmentButtonLabel,
attributes: {
href: this.newEnvironmentPath,
category: 'primary',
variant: 'confirm',
},
};
},
openReviewAppModal() {
if (!this.canSetupReviewApp) {
return null;
}
return {
text: this.$options.i18n.reviewAppButtonLabel,
attributes: {
category: 'secondary',
variant: 'confirm',
},
};
},
},
methods: {
showReviewAppModal() {
this.isReviewAppModalVisible = true;
},
},
};
</script>
<template>
<div>
<gl-tabs>
<enable-review-app-modal
v-if="canSetupReviewApp"
v-model="isReviewAppModalVisible"
:modal-id="$options.modalId"
data-testid="enable-review-app-modal"
/>
<gl-tabs
:action-secondary="addEnvironment"
:action-primary="openReviewAppModal"
@primary="showReviewAppModal"
>
<gl-tab>
<template #title>
<span>{{ __('Available') }}</span>

View File

@ -20,7 +20,6 @@ import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { getParameterValues } from '~/lib/utils/url_utility';
import { sprintf } from '~/locale';
import {
INVITE_MEMBERS_IN_COMMENT,
GROUP_FILTERS,
USERS_FILTER_ALL,
INVITE_MEMBERS_FOR_TASK,
@ -254,11 +253,6 @@ export default {
this.submitInviteMembers();
}
},
trackInvite() {
if (this.source === INVITE_MEMBERS_IN_COMMENT) {
this.trackEvent(INVITE_MEMBERS_IN_COMMENT, 'comment_invite_success');
}
},
trackinviteMembersForTask() {
const label = 'selected_tasks_to_be_done';
const property = this.selectedTasksToBeDone.join(',');
@ -312,7 +306,6 @@ export default {
promises.push(apiAddByUserId(this.id, this.addByUserIdPostData(usersToAddById)));
}
this.trackInvite();
this.trackinviteMembersForTask();
Promise.all(promises)

View File

@ -1,6 +1,5 @@
<script>
import { GlButton, GlLink, GlIcon } from '@gitlab/ui';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
import { TRIGGER_ELEMENT_BUTTON, TRIGGER_ELEMENT_SIDE_NAV } from '../constants';
@ -32,11 +31,6 @@ export default {
type: String,
required: true,
},
trackExperiment: {
type: String,
required: false,
default: undefined,
},
triggerElement: {
type: String,
required: false,
@ -72,9 +66,6 @@ export default {
return baseAttributes;
},
},
mounted() {
this.trackExperimentOnShow();
},
methods: {
checkTrigger(targetTriggerElement) {
return this.triggerElement === targetTriggerElement;
@ -82,12 +73,6 @@ export default {
openModal() {
eventHub.$emit('openModal', { inviteeType: 'members', source: this.triggerSource });
},
trackExperimentOnShow() {
if (this.trackExperiment) {
const tracking = new ExperimentTracking(this.trackExperiment);
tracking.event('comment_invite_shown');
}
},
},
TRIGGER_ELEMENT_BUTTON,
TRIGGER_ELEMENT_SIDE_NAV,

View File

@ -2,7 +2,6 @@ import { __, s__ } from '~/locale';
export const SEARCH_DELAY = 200;
export const INVITE_MEMBERS_IN_COMMENT = 'invite_members_in_comment';
export const INVITE_MEMBERS_FOR_TASK = {
minimum_access_level: 30,
name: 'invite_members_for_task',

View File

@ -52,14 +52,9 @@ export const loadCommits = async (projectPath, path, ref, offset) => {
}
// We fetch in batches of 25, so this ensures we don't refetch
Array.from(Array(COMMIT_BATCH_SIZE)).forEach((_, i) => {
addRequestedOffset(offset - i);
addRequestedOffset(offset + i);
});
Array.from(Array(COMMIT_BATCH_SIZE)).forEach((_, i) => addRequestedOffset(offset + i));
// Since a user could scroll either up or down, we want to support lazy loading in both directions
const commitsBatchUp = await fetchData(projectPath, path, ref, offset - COMMIT_BATCH_SIZE);
const commitsBatchDown = await fetchData(projectPath, path, ref, offset);
const commits = await fetchData(projectPath, path, ref, offset);
return commitsBatchUp.concat(commitsBatchDown);
return commits;
};

View File

@ -13,7 +13,7 @@ import {
import { escapeRegExp } from 'lodash';
import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.graphql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import { TREE_PAGE_SIZE } from '~/repository/constants';
import { TREE_PAGE_SIZE, ROW_APPEAR_DELAY } from '~/repository/constants';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@ -128,6 +128,7 @@ export default {
return {
commit: null,
hasRowAppeared: false,
delayedRowAppear: null,
};
},
computed: {
@ -202,14 +203,19 @@ export default {
rowAppeared() {
this.hasRowAppeared = true;
if (this.commitInfo) {
return;
}
if (this.glFeatures.lazyLoadCommits) {
this.$emit('row-appear', {
rowNumber: this.rowNumber,
hasCommit: Boolean(this.commitInfo),
});
this.delayedRowAppear = setTimeout(
() => this.$emit('row-appear', this.rowNumber),
ROW_APPEAR_DELAY,
);
}
},
rowDisappeared() {
clearTimeout(this.delayedRowAppear);
this.hasRowAppeared = false;
},
},

View File

@ -3,7 +3,12 @@ import paginatedTreeQuery from 'shared_queries/repository/paginated_tree.query.g
import createFlash from '~/flash';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { __ } from '../../locale';
import { TREE_PAGE_SIZE, TREE_INITIAL_FETCH_COUNT, TREE_PAGE_LIMIT } from '../constants';
import {
TREE_PAGE_SIZE,
TREE_INITIAL_FETCH_COUNT,
TREE_PAGE_LIMIT,
COMMIT_BATCH_SIZE,
} from '../constants';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
import { readmeFile } from '../utils/readme';
@ -151,11 +156,19 @@ export default {
.concat(data.trees.pageInfo, data.submodules.pageInfo, data.blobs.pageInfo)
.find(({ hasNextPage }) => hasNextPage);
},
loadCommitData({ rowNumber = 0, hasCommit } = {}) {
if (!this.glFeatures.lazyLoadCommits || hasCommit || isRequested(rowNumber)) {
handleRowAppear(rowNumber) {
if (!this.glFeatures.lazyLoadCommits || isRequested(rowNumber)) {
return;
}
// Since a user could scroll either up or down, we want to support lazy loading in both directions
this.loadCommitData(rowNumber);
if (rowNumber - COMMIT_BATCH_SIZE >= 0) {
this.loadCommitData(rowNumber - COMMIT_BATCH_SIZE);
}
},
loadCommitData(rowNumber) {
loadCommits(this.projectPath, this.path, this.ref, rowNumber)
.then(this.setCommitData)
.catch(() => {});
@ -182,7 +195,7 @@ export default {
:has-more="hasShowMore"
:commits="commits"
@showMore="handleShowMore"
@row-appear="loadCommitData"
@row-appear="handleRowAppear"
/>
<file-preview v-if="readme" :blob="readme" />
</div>

View File

@ -23,3 +23,5 @@ export const I18N_COMMIT_DATA_FETCH_ERROR = __('An error occurred while fetching
export const PDF_MAX_FILE_SIZE = 10000000; // 10 MB
export const PDF_MAX_PAGE_LIMIT = 50;
export const ROW_APPEAR_DELAY = 150;

View File

@ -1,18 +1,13 @@
<script>
import { GlButton, GlLink, GlLoadingIcon, GlSprintf, GlIcon } from '@gitlab/ui';
import { isExperimentVariant } from '~/experimentation/utils';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import { INVITE_MEMBERS_IN_COMMENT } from '~/invite_members/constants';
export default {
inviteMembersInComment: INVITE_MEMBERS_IN_COMMENT,
components: {
GlButton,
GlLink,
GlLoadingIcon,
GlSprintf,
GlIcon,
InviteMembersTrigger,
},
props: {
markdownDocsPath: {
@ -34,9 +29,6 @@ export default {
hasQuickActionsDocsPath() {
return this.quickActionsDocsPath !== '';
},
inviteCommentEnabled() {
return isExperimentVariant(INVITE_MEMBERS_IN_COMMENT, 'invite_member_link');
},
},
};
</script>
@ -67,16 +59,6 @@ export default {
</template>
</div>
<span v-if="canAttachFile" class="uploading-container">
<invite-members-trigger
v-if="inviteCommentEnabled"
classes="gl-mr-3 gl-vertical-align-text-bottom"
:display-text="s__('InviteMember|Invite Member')"
icon="assignee"
variant="link"
:track-experiment="$options.inviteMembersInComment"
:trigger-source="$options.inviteMembersInComment"
data-track-action="comment_invite_click"
/>
<span class="uploading-progress-container hide">
<gl-icon name="media" />
<span class="attaching-file-message"></span>

View File

@ -45,12 +45,12 @@
}
}
@mixin line-number-hover($color) {
background-color: $color;
border-color: darken($color, 5%);
@mixin line-number-hover {
background-color: $purple-100;
border-color: $purple-200;
a {
color: darken($color, 15%);
color: $gray-600;
}
}

View File

@ -22,7 +22,6 @@ $dark-highlight-bg: #ffe792;
$dark-highlight-color: $black;
$dark-pre-hll-bg: #373b41;
$dark-hll-bg: #373b41;
$dark-over-bg: #9f9ab5;
$dark-expanded-bg: #3e3e3e;
$dark-coverage: #b3e841;
$dark-no-coverage: #ff4f33;
@ -93,7 +92,7 @@ $dark-il: #de935f;
.file-line-num {
@include line-number-link($dark-line-num-color);
}
.line-numbers,
.diff-line-num {
background-color: $dark-main-bg;
@ -171,14 +170,14 @@ $dark-il: #de935f;
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($dark-over-bg);
@include line-number-hover;
}
}
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
@include line-number-hover($dark-over-bg);
@include line-number-hover;
}
}

View File

@ -15,7 +15,6 @@ $monokai-line-empty-bg: #49483e;
$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080;
$monokai-highlight-bg: #ffe792;
$monokai-over-bg: #9f9ab5;
$monokai-expanded-bg: #3e3e3e;
$monokai-coverage: #a6e22e;
$monokai-no-coverage: #fd971f;
@ -172,14 +171,14 @@ $monokai-gh: #75715e;
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($monokai-over-bg);
@include line-number-hover;
}
}
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
@include line-number-hover($monokai-over-bg);
@include line-number-hover;
}
}

View File

@ -43,7 +43,6 @@
}
// Diff line
$none-over-bg: #ded7fc;
$none-expanded-border: #e0e0e0;
$none-expanded-bg: #e0e0e0;
@ -69,7 +68,7 @@
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($none-over-bg);
@include line-number-hover;
}
}
@ -88,7 +87,7 @@
&.is-over,
&.hll:not(.empty-cell).is-over {
@include line-number-hover($none-over-bg);
@include line-number-hover;
}
&.hll:not(.empty-cell) {

View File

@ -19,7 +19,6 @@ $solarized-dark-line-color-new: #5a766c;
$solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652;
$solarized-dark-over-bg: #9f9ab5;
$solarized-dark-expanded-bg: #010d10;
$solarized-dark-coverage: #859900;
$solarized-dark-no-coverage: #cb4b16;
@ -151,7 +150,7 @@ $solarized-dark-il: #2aa198;
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($solarized-dark-over-bg);
@include line-number-hover;
}
}
@ -182,7 +181,7 @@ $solarized-dark-il: #2aa198;
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
@include line-number-hover($solarized-dark-over-bg);
@include line-number-hover;
}
}

View File

@ -20,7 +20,6 @@ $solarized-light-line-color-new: #a1a080;
$solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5;
$solarized-light-over-bg: #ded7fc;
$solarized-light-expanded-border: #d2cdbd;
$solarized-light-expanded-bg: #ece6d4;
$solarized-light-coverage: #859900;
@ -171,7 +170,7 @@ $solarized-light-il: #2aa198;
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell) {
@include line-number-hover($solarized-light-over-bg);
@include line-number-hover;
}
}
@ -190,7 +189,7 @@ $solarized-light-il: #2aa198;
.diff-line-num {
&.is-over,
&.hll:not(.empty-cell).is-over {
@include line-number-hover($solarized-light-over-bg);
@include line-number-hover;
}
}

View File

@ -9,7 +9,6 @@ $white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
@ -131,7 +130,7 @@ pre.code,
.diff-grid-left:hover,
.diff-grid-right:hover {
.diff-line-num:not(.empty-cell):not(.conflict_marker_their):not(.conflict_marker_our) {
@include line-number-hover($white-over-bg);
@include line-number-hover;
}
}
@ -156,7 +155,7 @@ pre.code,
&.is-over,
&.hll:not(.empty-cell).is-over {
@include line-number-hover($white-over-bg);
@include line-number-hover;
}
&.hll:not(.empty-cell) {

View File

@ -329,16 +329,6 @@ table.u2f-registrations {
}
}
.email-badge {
display: inline;
margin-right: $gl-padding / 2;
.email-badge-email {
display: inline;
margin-right: $gl-padding / 4;
}
}
.edit-user {
svg {
fill: $gl-text-color-secondary;

View File

@ -52,15 +52,6 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
experiment(:invite_members_in_comment, namespace: @project.root_ancestor) do |experiment_instance|
experiment_instance.exclude! unless helpers.can_admin_project_member?(@project)
experiment_instance.use {}
experiment_instance.try(:invite_member_link) {}
experiment_instance.track(:view, property: @project.root_ancestor.id.to_s)
end
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]

View File

@ -47,15 +47,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
push_frontend_feature_flag(:diff_searching_usage_data, @project, default_enabled: :yaml)
experiment(:invite_members_in_comment, namespace: @project.root_ancestor) do |experiment_instance|
experiment_instance.exclude! unless helpers.can_admin_project_member?(@project)
experiment_instance.use {}
experiment_instance.try(:invite_member_link) {}
experiment_instance.track(:view, property: @project.root_ancestor.id.to_s)
end
end
before_action do

View File

@ -1,13 +0,0 @@
.modal.js-regenerate-public-ssh-key-confirm-modal{ tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
%h3.modal-title.page-title
Regenerate public SSH key?
%button.close.js-cancel{ type: 'button', 'data-dismiss': 'modal', 'aria-label' => _('Close') }
%span{ 'aria-hidden': true } &times;
.modal-body
%p= _('Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again.')
.form-actions.modal-footer
= button_tag _('Cancel'), type: 'button', class: 'btn gl-button js-cancel'
= button_tag _('Regenerate key'), type: 'button', class: 'btn gl-button btn-inverted btn-warning js-confirm'

View File

@ -1,8 +1,5 @@
- css_classes = %w(badge gl-badge)
- css_classes << (verified ? 'badge-success': 'badge-danger')
- variant = verified ? :success : :danger
- text = verified ? _('Verified') : _('Unverified')
.email-badge
.email-badge-email= email
%div{ class: css_classes }
= text
= email
= gl_badge_tag text, { variant: variant }, { class: 'gl-ml-3' }

View File

@ -8,8 +8,9 @@ begin
raise "Required: METRICS_SERVER_TARGET=[sidekiq]" unless target == 'sidekiq'
metrics_dir = ENV["prometheus_multiproc_dir"] || File.absolute_path("tmp/prometheus_multiproc_dir/#{target}")
wipe_metrics_dir = Gitlab::Utils.to_boolean(ENV['WIPE_METRICS_DIR']) || false
# Re-raise exceptions in threads on the main thread.
Thread.abort_on_exception = true
MetricsServer.new(target, metrics_dir).start
MetricsServer.new(target, metrics_dir, wipe_metrics_dir).start
end

View File

@ -1,8 +0,0 @@
---
name: invite_members_in_comment
introduced_by_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51400'
rollout_issue_url: 'https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/300'
milestone: '13.10'
type: experiment
group: group::expansion
default_enabled: false

View File

@ -70,11 +70,17 @@ module Gitlab
end
def self.process_alive?(pid)
return false if pid.nil?
# Signal 0 tests whether the process exists and we have access to send signals
# but is otherwise a noop (doesn't actually send a signal to the process)
signal(pid, 0)
end
def self.process_died?(pid)
!process_alive?(pid)
end
def self.write_pid(path)
File.open(path, 'w') do |handle|
handle.write(Process.pid.to_s)

View File

@ -4619,9 +4619,6 @@ msgstr ""
msgid "Are you sure you want to re-deploy this environment?"
msgstr ""
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to reindex?"
msgstr ""
@ -19317,9 +19314,6 @@ msgstr ""
msgid "InviteMember|Add members to this project and start collaborating with your team."
msgstr ""
msgid "InviteMember|Invite Member"
msgstr ""
msgid "InviteMember|Invite Members (optional)"
msgstr ""
@ -28858,9 +28852,6 @@ msgstr ""
msgid "Regenerate instance ID"
msgstr ""
msgid "Regenerate key"
msgstr ""
msgid "Regenerate recovery codes"
msgstr ""

View File

@ -13,6 +13,7 @@ require 'rack'
require_relative 'settings_overrides'
require_relative '../lib/gitlab/daemon'
require_relative '../lib/gitlab/utils'
require_relative '../lib/gitlab/utils/strong_memoize'
require_relative '../lib/prometheus/cleanup_multiproc_dir_service'
require_relative '../lib/gitlab/metrics/prometheus'

View File

@ -1,16 +1,17 @@
# frozen_string_literal: true
require_relative '../config/bundler_setup'
require_relative '../config/boot'
require_relative 'dependencies'
class MetricsServer # rubocop:disable Gitlab/NamespacedClass
class << self
def spawn(target, gitlab_config: nil)
def spawn(target, gitlab_config: nil, wipe_metrics_dir: false)
cmd = "#{Rails.root}/bin/metrics-server"
env = {
'METRICS_SERVER_TARGET' => target,
'GITLAB_CONFIG' => gitlab_config
'GITLAB_CONFIG' => gitlab_config,
'WIPE_METRICS_DIR' => wipe_metrics_dir.to_s
}
Process.spawn(env, cmd, err: $stderr, out: $stdout).tap do |pid|
@ -19,9 +20,10 @@ class MetricsServer # rubocop:disable Gitlab/NamespacedClass
end
end
def initialize(target, metrics_dir)
def initialize(target, metrics_dir, wipe_metrics_dir)
@target = target
@metrics_dir = metrics_dir
@wipe_metrics_dir = wipe_metrics_dir
end
def start
@ -30,7 +32,7 @@ class MetricsServer # rubocop:disable Gitlab/NamespacedClass
end
FileUtils.mkdir_p(@metrics_dir, mode: 0700)
::Prometheus::CleanupMultiprocDirService.new.execute
::Prometheus::CleanupMultiprocDirService.new.execute if @wipe_metrics_dir
settings = Settings.monitoring.sidekiq_exporter
exporter_class = "Gitlab::Metrics::Exporter::#{@target.camelize}Exporter".constantize

View File

@ -77,7 +77,7 @@ module QA
install_ingress
# need to wait since the ingress-nginx service has an initial delay set of 10 seconds
sleep 10
sleep 12
ingress_ip = `kubectl get svc --all-namespaces --no-headers=true -l app.kubernetes.io/name=ingress-nginx -o custom-columns=:'status.loadBalancer.ingress[0].ip' | grep -v 'none'`
QA::Runtime::Logger.debug "Has ingress address set to: #{ingress_ip}"
ingress_ip

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require_relative '../config/bundler_setup'
require 'optparse'
require 'logger'
require 'time'
@ -12,6 +14,7 @@ require_relative '../lib/gitlab/sidekiq_config/cli_methods'
require_relative '../lib/gitlab/sidekiq_config/worker_matcher'
require_relative '../lib/gitlab/sidekiq_logging/json_formatter'
require_relative '../lib/gitlab/process_management'
require_relative '../metrics_server/metrics_server'
require_relative 'sidekiq_cluster'
module Gitlab
@ -89,6 +92,8 @@ module Gitlab
@logger.info("Starting cluster with #{queue_groups.length} processes")
end
start_metrics_server(wipe_metrics_dir: true)
@processes = SidekiqCluster.start(
queue_groups,
env: @environment,
@ -154,17 +159,69 @@ module Gitlab
while @alive
sleep(@interval)
if metrics_server_enabled? && ProcessManagement.process_died?(@metrics_server_pid)
@logger.warn('Metrics server went away')
start_metrics_server(wipe_metrics_dir: false)
end
unless ProcessManagement.all_alive?(@processes)
# If a child process died we'll just terminate the whole cluster. It's up to
# runit and such to then restart the cluster.
@logger.info('A worker terminated, shutting down the cluster')
stop_metrics_server
ProcessManagement.signal_processes(@processes, :TERM)
break
end
end
end
def start_metrics_server(wipe_metrics_dir: false)
return unless metrics_server_enabled?
@logger.info("Starting metrics server on port #{sidekiq_exporter_port}")
@metrics_server_pid = MetricsServer.spawn('sidekiq', wipe_metrics_dir: wipe_metrics_dir)
end
def sidekiq_exporter_enabled?
::Settings.monitoring.sidekiq_exporter.enabled
rescue Settingslogic::MissingSetting
nil
end
def exporter_has_a_unique_port?
# In https://gitlab.com/gitlab-org/gitlab/-/issues/345802 we added settings for sidekiq_health_checks.
# These settings default to the same values as sidekiq_exporter for backwards compatibility.
# If a different port for sidekiq_health_checks has been set up, we know that the
# user wants to serve health checks and metrics from different servers.
return false if sidekiq_health_check_port.nil? || sidekiq_exporter_port.nil?
sidekiq_exporter_port != sidekiq_health_check_port
end
def sidekiq_exporter_port
::Settings.monitoring.sidekiq_exporter.port
rescue Settingslogic::MissingSetting
nil
end
def sidekiq_health_check_port
::Settings.monitoring.sidekiq_health_checks.port
rescue Settingslogic::MissingSetting
nil
end
def metrics_server_enabled?
!@dryrun && sidekiq_exporter_enabled? && exporter_has_a_unique_port?
end
def stop_metrics_server
return unless @metrics_server_pid
@logger.info("Stopping metrics server (PID #{@metrics_server_pid})")
ProcessManagement.signal(@metrics_server_pid, :TERM)
end
def option_parser
OptionParser.new do |opt|
opt.banner = "#{File.basename(__FILE__)} [QUEUE,QUEUE] [QUEUE] ... [OPTIONS]"

View File

@ -1,6 +0,0 @@
# rubocop:disable Naming/FileName
# frozen_string_literal: true
require 'shellwords'
# rubocop:enable Naming/FileName

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true
require_relative 'dependencies'
require_relative '../lib/gitlab/process_management'
module Gitlab
@ -67,14 +66,19 @@ module Gitlab
return
end
pid = Process.spawn(
{ 'ENABLE_SIDEKIQ_CLUSTER' => '1',
'SIDEKIQ_WORKER_ID' => worker_id.to_s },
*cmd,
pgroup: true,
err: $stderr,
out: $stdout
)
# We need to remove Bundler specific env vars, since otherwise the
# child process will think we are passing an alternative Gemfile
# and will clear and reset LOAD_PATH.
pid = Bundler.with_original_env do
Process.spawn(
{ 'ENABLE_SIDEKIQ_CLUSTER' => '1',
'SIDEKIQ_WORKER_ID' => worker_id.to_s },
*cmd,
pgroup: true,
err: $stderr,
out: $stdout
)
end
ProcessManagement.wait_async(pid)

View File

@ -29,7 +29,7 @@ RSpec.describe 'bin/metrics-server', :aggregate_failures do
config_file.write(YAML.dump(config))
config_file.close
@pid = MetricsServer.spawn('sidekiq', gitlab_config: config_file.path)
@pid = MetricsServer.spawn('sidekiq', gitlab_config: config_file.path, wipe_metrics_dir: true)
end
after do

View File

@ -12,8 +12,23 @@ RSpec.describe Gitlab::SidekiqCluster::CLI do # rubocop:disable RSpec/FilePath
{ env: 'test', directory: Dir.pwd, max_concurrency: 50, min_concurrency: 0, dryrun: false, timeout: timeout }
end
let(:sidekiq_exporter_enabled) { false }
let(:sidekiq_exporter_port) { '3807' }
let(:sidekiq_health_checks_port) { '3807' }
before do
stub_env('RAILS_ENV', 'test')
stub_config(
monitoring: {
sidekiq_exporter: {
enabled: sidekiq_exporter_enabled,
port: sidekiq_exporter_port
},
sidekiq_health_checks: {
port: sidekiq_health_checks_port
}
}
)
end
describe '#run' do
@ -241,6 +256,163 @@ RSpec.describe Gitlab::SidekiqCluster::CLI do # rubocop:disable RSpec/FilePath
end
end
end
context 'metrics server' do
context 'starting the server' do
context 'without --dryrun' do
context 'when there are no sidekiq_health_checks settings set' do
before do
stub_config(
monitoring: {
sidekiq_exporter: {
enabled: true,
port: sidekiq_exporter_port
}
}
)
allow(Gitlab::SidekiqCluster).to receive(:start)
allow(cli).to receive(:write_pid)
allow(cli).to receive(:trap_signals)
allow(cli).to receive(:start_loop)
end
it 'does not start a sidekiq metrics server' do
expect(MetricsServer).not_to receive(:spawn)
cli.run(%w(foo))
end
it 'rescues Settingslogic::MissingSetting' do
expect { cli.run(%w(foo)) }.not_to raise_error(Settingslogic::MissingSetting)
end
end
context 'when the sidekiq_exporter.port setting is not set' do
before do
stub_config(
monitoring: {
sidekiq_exporter: {
enabled: true
},
sidekiq_health_checks: {
port: sidekiq_health_checks_port
}
}
)
allow(Gitlab::SidekiqCluster).to receive(:start)
allow(cli).to receive(:write_pid)
allow(cli).to receive(:trap_signals)
allow(cli).to receive(:start_loop)
end
it 'does not start a sidekiq metrics server' do
expect(MetricsServer).not_to receive(:spawn)
cli.run(%w(foo))
end
it 'rescues Settingslogic::MissingSetting' do
expect { cli.run(%w(foo)) }.not_to raise_error(Settingslogic::MissingSetting)
end
end
context 'when sidekiq_exporter.enabled setting is not set' do
before do
stub_config(
monitoring: {
sidekiq_exporter: {},
sidekiq_health_checks: {
port: sidekiq_health_checks_port
}
}
)
allow(Gitlab::SidekiqCluster).to receive(:start)
allow(cli).to receive(:write_pid)
allow(cli).to receive(:trap_signals)
allow(cli).to receive(:start_loop)
end
it 'does not start a sidekiq metrics server' do
expect(MetricsServer).not_to receive(:spawn)
cli.run(%w(foo))
end
end
using RSpec::Parameterized::TableSyntax
where(:sidekiq_exporter_enabled, :sidekiq_exporter_port, :sidekiq_health_checks_port, :start_metrics_server) do
true | '3807' | '3907' | true
true | '3807' | '3807' | false
false | '3807' | '3907' | false
false | '3807' | '3907' | false
end
with_them do
before do
allow(Gitlab::SidekiqCluster).to receive(:start)
allow(cli).to receive(:write_pid)
allow(cli).to receive(:trap_signals)
allow(cli).to receive(:start_loop)
end
specify do
if start_metrics_server
expect(MetricsServer).to receive(:spawn).with('sidekiq', wipe_metrics_dir: true)
else
expect(MetricsServer).not_to receive(:spawn)
end
cli.run(%w(foo))
end
end
end
context 'with --dryrun set' do
let(:sidekiq_exporter_enabled) { true }
it 'does not start the server' do
expect(MetricsServer).not_to receive(:spawn)
cli.run(%w(foo --dryrun))
end
end
end
context 'supervising the server' do
let(:sidekiq_exporter_enabled) { true }
let(:sidekiq_health_checks_port) { '3907' }
before do
allow(cli).to receive(:sleep).with(a_kind_of(Numeric))
allow(MetricsServer).to receive(:spawn).with('sidekiq', wipe_metrics_dir: false).and_return(99)
cli.start_metrics_server
end
it 'stops the metrics server when one of the processes has been terminated' do
allow(Gitlab::ProcessManagement).to receive(:process_died?).and_return(false)
allow(Gitlab::ProcessManagement).to receive(:all_alive?).with(an_instance_of(Array)).and_return(false)
allow(Gitlab::ProcessManagement).to receive(:signal_processes).with(an_instance_of(Array), :TERM)
expect(Process).to receive(:kill).with(:TERM, 99)
cli.start_loop
end
it 'starts the metrics server when it is down' do
allow(Gitlab::ProcessManagement).to receive(:process_died?).and_return(true)
allow(Gitlab::ProcessManagement).to receive(:all_alive?).with(an_instance_of(Array)).and_return(false)
allow(cli).to receive(:stop_metrics_server)
expect(MetricsServer).to receive(:spawn).with('sidekiq', wipe_metrics_dir: false)
cli.start_loop
end
end
end
end
describe '#write_pid' do

View File

@ -201,32 +201,6 @@ RSpec.describe Projects::IssuesController do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['issue_email_participants']).to contain_exactly({ "email" => participants[0].email }, { "email" => participants[1].email })
end
context 'with the invite_members_in_comment experiment', :experiment do
context 'when user can invite' do
before do
stub_experiments(invite_members_in_comment: :invite_member_link)
project.add_maintainer(user)
end
it 'assigns the candidate experience and tracks the event' do
expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s)
.for(:invite_member_link)
.with_context(namespace: project.root_ancestor)
.on_next_instance
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
end
end
context 'when user can not invite' do
it 'does not track the event' do
expect(experiment(:invite_members_in_comment)).not_to track(:view)
get :show, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
end
end
end
end
describe 'GET #new' do

View File

@ -42,32 +42,6 @@ RSpec.describe Projects::MergeRequestsController do
get :show, params: params.merge(extra_params)
end
context 'with the invite_members_in_comment experiment', :experiment do
context 'when user can invite' do
before do
stub_experiments(invite_members_in_comment: :invite_member_link)
project.add_maintainer(user)
end
it 'assigns the candidate experience and tracks the event' do
expect(experiment(:invite_members_in_comment)).to track(:view, property: project.root_ancestor.id.to_s)
.for(:invite_member_link)
.with_context(namespace: project.root_ancestor)
.on_next_instance
go
end
end
context 'when user can not invite' do
it 'does not track the event' do
expect(experiment(:invite_members_in_comment)).not_to track(:view)
go
end
end
end
context 'with view param' do
before do
go(view: 'parallel')

View File

@ -1,25 +0,0 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe "User invites from a comment", :js do
let_it_be(:project) { create(:project_empty_repo, :public) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:user) { project.owner }
before do
sign_in(user)
end
it "launches the invite modal from invite link on a comment" do
stub_experiments(invite_members_in_comment: :invite_member_link)
visit project_issue_path(project, issue)
page.within(".new-note") do
click_button 'Invite Member'
end
expect(page).to have_content("You're inviting members to the")
end
end

View File

@ -1,25 +0,0 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe "User invites from a comment", :js do
let_it_be(:project) { create(:project, :public, :repository) }
let_it_be(:merge_request) { create(:merge_request, source_project: project) }
let_it_be(:user) { project.owner }
before do
sign_in(user)
end
it "launches the invite modal from invite link on a comment" do
stub_experiments(invite_members_in_comment: :invite_member_link)
visit project_merge_request_path(project, merge_request)
page.within(".new-note") do
click_button 'Invite Member'
end
expect(page).to have_content("You're inviting members to the")
end
end

View File

@ -1,10 +1,12 @@
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import EnableReviewAppButton from '~/environments/components/enable_review_app_modal.vue';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';
describe('Enable Review App Button', () => {
let wrapper;
let modal;
afterEach(() => {
wrapper.destroy();
@ -16,12 +18,15 @@ describe('Enable Review App Button', () => {
shallowMount(EnableReviewAppButton, {
propsData: {
modalId: 'fake-id',
visible: true,
},
provide: {
defaultBranchName: 'main',
},
}),
);
modal = wrapper.findComponent(GlModal);
});
it('renders the defaultBranchName copy', () => {
@ -32,5 +37,15 @@ describe('Enable Review App Button', () => {
it('renders the copyToClipboard button', () => {
expect(wrapper.findComponent(ModalCopyButton).exists()).toBe(true);
});
it('emits change events from the modal up', () => {
modal.vm.$emit('change', false);
expect(wrapper.emitted('change')).toEqual([[false]]);
});
it('passes visible to the modal', () => {
expect(modal.props('visible')).toBe(true);
});
});
});

View File

@ -1,8 +1,9 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mount } from '@vue/test-utils';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { s__ } from '~/locale';
import EnvironmentsApp from '~/environments/components/new_environments_app.vue';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
@ -22,7 +23,16 @@ describe('~/environments/components/new_environments_app.vue', () => {
return createMockApollo([], mockResolvers);
};
const createWrapper = (apolloProvider) => mount(EnvironmentsApp, { apolloProvider });
const createWrapper = ({ provide = {}, apolloProvider } = {}) =>
mountExtended(EnvironmentsApp, {
provide: {
newEnvironmentPath: '/environments/new',
canCreateEnvironment: true,
defaultBranchName: 'main',
...provide,
},
apolloProvider,
});
beforeEach(() => {
environmentAppMock = jest.fn();
@ -37,7 +47,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentAppMock.mockReturnValue(resolvedEnvironmentsApp);
environmentFolderMock.mockReturnValue(resolvedFolder);
const apolloProvider = createApolloProvider();
wrapper = createWrapper(apolloProvider);
wrapper = createWrapper({ apolloProvider });
await waitForPromises();
await Vue.nextTick();
@ -47,4 +57,66 @@ describe('~/environments/components/new_environments_app.vue', () => {
expect(text).toContainEqual(expect.stringMatching('review'));
expect(text).not.toContainEqual(expect.stringMatching('production'));
});
it('should show a button to create a new environment', async () => {
environmentAppMock.mockReturnValue(resolvedEnvironmentsApp);
environmentFolderMock.mockReturnValue(resolvedFolder);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider });
await waitForPromises();
await Vue.nextTick();
const button = wrapper.findByRole('link', { name: s__('Environments|New environment') });
expect(button.attributes('href')).toBe('/environments/new');
});
it('should not show a button to create a new environment if the user has no permissions', async () => {
environmentAppMock.mockReturnValue(resolvedEnvironmentsApp);
environmentFolderMock.mockReturnValue(resolvedFolder);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({
apolloProvider,
provide: { canCreateEnvironment: false, newEnvironmentPath: '' },
});
await waitForPromises();
await Vue.nextTick();
const button = wrapper.findByRole('link', { name: s__('Environments|New environment') });
expect(button.exists()).toBe(false);
});
it('should show a button to open the review app modal', async () => {
environmentAppMock.mockReturnValue(resolvedEnvironmentsApp);
environmentFolderMock.mockReturnValue(resolvedFolder);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider });
await waitForPromises();
await Vue.nextTick();
const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') });
button.trigger('click');
await Vue.nextTick();
expect(wrapper.findByText(s__('ReviewApp|Enable Review App')).exists()).toBe(true);
});
it('should not show a button to open the review app modal if review apps are configured', async () => {
environmentAppMock.mockReturnValue({
...resolvedEnvironmentsApp,
reviewApp: { canSetupReviewApp: false },
});
environmentFolderMock.mockReturnValue(resolvedFolder);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider });
await waitForPromises();
await Vue.nextTick();
const button = wrapper.findByRole('button', { name: s__('Environments|Enable review app') });
expect(button.exists()).toBe(false);
});
});

View File

@ -17,7 +17,6 @@ import InviteMembersModal from '~/invite_members/components/invite_members_modal
import ModalConfetti from '~/invite_members/components/confetti.vue';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import {
INVITE_MEMBERS_IN_COMMENT,
INVITE_MEMBERS_FOR_TASK,
CANCEL_BUTTON_TEXT,
INVITE_BUTTON_TEXT,
@ -746,7 +745,6 @@ describe('InviteMembersModal', () => {
wrapper.vm.$toast = { show: jest.fn() };
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
jest.spyOn(wrapper.vm, 'trackInvite');
});
describe('when triggered from regular mounting', () => {
@ -864,31 +862,6 @@ describe('InviteMembersModal', () => {
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({});
});
it('tracks the invite', () => {
eventHub.$emit('openModal', { inviteeType: 'members', source: INVITE_MEMBERS_IN_COMMENT });
clickInviteButton();
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('comment_invite_success');
});
it('does not track invite for unknown source', () => {
eventHub.$emit('openModal', { inviteeType: 'members', source: 'unknown' });
clickInviteButton();
expect(ExperimentTracking).not.toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT);
});
it('does not track invite undefined source', () => {
eventHub.$emit('openModal', { inviteeType: 'members' });
clickInviteButton();
expect(ExperimentTracking).not.toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT);
});
it('tracks the view for learn_gitlab source', () => {
eventHub.$emit('openModal', { inviteeType: 'members', source: LEARN_GITLAB });

View File

@ -1,6 +1,5 @@
import { GlButton, GlLink, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ExperimentTracking from '~/experimentation/experiment_tracking';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import eventHub from '~/invite_members/event_hub';
import { TRIGGER_ELEMENT_BUTTON, TRIGGER_ELEMENT_SIDE_NAV } from '~/invite_members/constants';
@ -79,19 +78,6 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => {
});
describe('tracking', () => {
it('tracks on mounting', () => {
createComponent({ trackExperiment: '_track_experiment_' });
expect(ExperimentTracking).toHaveBeenCalledWith('_track_experiment_');
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('comment_invite_shown');
});
it('does not track on mounting', () => {
createComponent();
expect(ExperimentTracking).not.toHaveBeenCalledWith('_track_experiment_');
});
it('does not add tracking attributes', () => {
createComponent();

View File

@ -52,13 +52,6 @@ describe('commits service', () => {
expect(axios.get.mock.calls.length).toEqual(1);
});
it('calls axios get twice if an offset is larger than 25', async () => {
await requestCommits(100);
expect(axios.get.mock.calls[0][1]).toEqual({ params: { format: 'json', offset: 75 } });
expect(axios.get.mock.calls[1][1]).toEqual({ params: { format: 'json', offset: 100 } });
});
it('updates the list of requested offsets', async () => {
await requestCommits(200);

View File

@ -4,6 +4,7 @@ import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import TableRow from '~/repository/components/table/row.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { FILE_SYMLINK_MODE } from '~/vue_shared/constants';
import { ROW_APPEAR_DELAY } from '~/repository/constants';
const COMMIT_MOCK = { lockLabel: 'Locked by Root', committedDate: '2019-01-01' };
@ -17,12 +18,12 @@ function factory(propsData = {}) {
vm = shallowMount(TableRow, {
propsData: {
commitInfo: COMMIT_MOCK,
...propsData,
name: propsData.path,
projectPath: 'gitlab-org/gitlab-ce',
url: `https://test.com`,
totalEntries: 10,
commitInfo: COMMIT_MOCK,
rowNumber: 123,
},
directives: {
@ -251,6 +252,8 @@ describe('Repository table row component', () => {
});
describe('row visibility', () => {
beforeAll(() => jest.useFakeTimers());
beforeEach(() => {
factory({
id: '1',
@ -258,18 +261,20 @@ describe('Repository table row component', () => {
path: 'test',
type: 'tree',
currentPath: '/',
commitInfo: null,
});
});
it('emits a `row-appear` event', () => {
afterAll(() => jest.useRealTimers());
it('emits a `row-appear` event', async () => {
findIntersectionObserver().vm.$emit('appear');
expect(vm.emitted('row-appear')).toEqual([
[
{
hasCommit: true,
rowNumber: 123,
},
],
]);
jest.runAllTimers();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), ROW_APPEAR_DELAY);
expect(vm.emitted('row-appear')).toEqual([[123]]);
});
});
});

View File

@ -190,14 +190,28 @@ describe('Repository table component', () => {
});
});
it('loads commit data when row-appear event is emitted', () => {
describe('commit data', () => {
const path = 'some/path';
const rowNumber = 1;
factory(path);
findFileTable().vm.$emit('row-appear', { hasCommit: false, rowNumber });
it('loads commit data for both top and bottom batches when row-appear event is emitted', () => {
const rowNumber = 50;
expect(isRequested).toHaveBeenCalledWith(rowNumber);
expect(loadCommits).toHaveBeenCalledWith('', path, '', rowNumber);
factory(path);
findFileTable().vm.$emit('row-appear', rowNumber);
expect(isRequested).toHaveBeenCalledWith(rowNumber);
expect(loadCommits.mock.calls).toEqual([
['', path, '', rowNumber],
['', path, '', rowNumber - 25],
]);
});
it('loads commit data once if rowNumber is zero', () => {
factory(path);
findFileTable().vm.$emit('row-appear', 0);
expect(loadCommits.mock.calls).toEqual([['', path, '', 0]]);
});
});
});

View File

@ -1,24 +1,17 @@
import { mount } from '@vue/test-utils';
import { isExperimentVariant } from '~/experimentation/utils';
import InviteMembersTrigger from '~/invite_members/components/invite_members_trigger.vue';
import { INVITE_MEMBERS_IN_COMMENT } from '~/invite_members/constants';
import Toolbar from '~/vue_shared/components/markdown/toolbar.vue';
jest.mock('~/experimentation/utils', () => ({ isExperimentVariant: jest.fn() }));
describe('toolbar', () => {
let wrapper;
const createMountedWrapper = (props = {}) => {
wrapper = mount(Toolbar, {
propsData: { markdownDocsPath: '', ...props },
stubs: { 'invite-members-trigger': true },
});
};
afterEach(() => {
wrapper.destroy();
isExperimentVariant.mockReset();
});
describe('user can attach file', () => {
@ -40,36 +33,4 @@ describe('toolbar', () => {
expect(wrapper.vm.$el.querySelector('.uploading-container')).toBeNull();
});
});
describe('user can invite member', () => {
const findInviteLink = () => wrapper.find(InviteMembersTrigger);
beforeEach(() => {
isExperimentVariant.mockReturnValue(true);
createMountedWrapper();
});
it('should render the invite members trigger', () => {
expect(findInviteLink().exists()).toBe(true);
});
it('should have correct props', () => {
expect(findInviteLink().props().displayText).toBe('Invite Member');
expect(findInviteLink().props().trackExperiment).toBe(INVITE_MEMBERS_IN_COMMENT);
expect(findInviteLink().props().triggerSource).toBe(INVITE_MEMBERS_IN_COMMENT);
});
});
describe('user can not invite member', () => {
const findInviteLink = () => wrapper.find(InviteMembersTrigger);
beforeEach(() => {
isExperimentVariant.mockReturnValue(false);
createMountedWrapper();
});
it('should render the invite members trigger', () => {
expect(findInviteLink().exists()).toBe(false);
});
});
});

View File

@ -78,7 +78,7 @@ RSpec.describe Gitlab::ProcessManagement do
end
describe '.process_alive?' do
it 'returns true if the proces is alive' do
it 'returns true if the process is alive' do
process = Process.pid
expect(described_class.process_alive?(process)).to eq(true)
@ -89,6 +89,32 @@ RSpec.describe Gitlab::ProcessManagement do
expect(described_class.process_alive?(process)).to eq(false)
end
it 'returns false when no pid is given' do
process = nil
expect(described_class.process_alive?(process)).to eq(false)
end
end
describe '.process_died?' do
it 'returns false if the process is alive' do
process = Process.pid
expect(described_class.process_died?(process)).to eq(false)
end
it 'returns true when a thread was not alive' do
process = -2
expect(described_class.process_died?(process)).to eq(true)
end
it 'returns true when no pid is given' do
process = nil
expect(described_class.process_died?(process)).to eq(true)
end
end
describe '.pids_alive' do

View File

@ -12,7 +12,8 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
let(:env) do
{
'METRICS_SERVER_TARGET' => 'sidekiq',
'GITLAB_CONFIG' => nil
'GITLAB_CONFIG' => nil,
'WIPE_METRICS_DIR' => 'false'
}
end
@ -32,7 +33,7 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
let(:metrics_dir) { Dir.mktmpdir }
let(:settings_double) { double(:settings, sidekiq_exporter: {}) }
subject(:metrics_server) { described_class.new('fake', metrics_dir)}
subject(:metrics_server) { described_class.new('fake', metrics_dir, true)}
before do
stub_env('prometheus_multiproc_dir', metrics_dir)
@ -42,6 +43,7 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
end
after do
::Prometheus::CleanupMultiprocDirService.new.execute
Dir.rmdir(metrics_dir)
end
@ -59,10 +61,24 @@ RSpec.describe MetricsServer do # rubocop:disable RSpec/FilePath
metrics_server.start
end
it 'removes any old metrics files' do
FileUtils.touch("#{metrics_dir}/remove_this.db")
context 'when wipe_metrics_dir is true' do
subject(:metrics_server) { described_class.new('fake', metrics_dir, true)}
expect { metrics_server.start }.to change { Dir.empty?(metrics_dir) }.from(false).to(true)
it 'removes any old metrics files' do
FileUtils.touch("#{metrics_dir}/remove_this.db")
expect { metrics_server.start }.to change { Dir.empty?(metrics_dir) }.from(false).to(true)
end
end
context 'when wipe_metrics_dir is false' do
subject(:metrics_server) { described_class.new('fake', metrics_dir, false)}
it 'does not remove any old metrics files' do
FileUtils.touch("#{metrics_dir}/remove_this.db")
expect { metrics_server.start }.not_to change { Dir.empty?(metrics_dir) }.from(false)
end
end
it 'starts a metrics server' do

View File

@ -13,6 +13,8 @@ RSpec.describe Gitlab::SidekiqCluster do # rubocop:disable RSpec/FilePath
out: $stdout
}
expect(Bundler).to receive(:with_original_env).and_call_original.twice
expect(Process).to receive(:spawn).ordered.with({
"ENABLE_SIDEKIQ_CLUSTER" => "1",
"SIDEKIQ_WORKER_ID" => "0"

View File

@ -28,7 +28,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
.to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
.to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,metrics_server,models,policies,presenters,rack_servers,replicators,routing,rubocop,scripts,serializers,services,sidekiq,sidekiq_cluster,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb")
end
end
@ -110,7 +110,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
.to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
.to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|metrics_server|models|policies|presenters|rack_servers|replicators|routing|rubocop|scripts|serializers|services|sidekiq|sidekiq_cluster|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)})
end
end

View File

@ -45,6 +45,7 @@ module Quality
serializers
services
sidekiq
sidekiq_cluster
spam
support_specs
tasks