Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-05 09:10:02 +00:00
parent 78bc17257c
commit 1c568d834d
37 changed files with 502 additions and 383 deletions

View file

@ -70,7 +70,7 @@ Are there any other stages or teams involved that need to be kept in the loop?
- [ ] Announce on the issue an estimated time this will be enabled on GitLab.com
- [ ] Check if the feature flag change needs to be accompagnied with a
- [ ] Check if the feature flag change needs to be accompanied with a
[change management
issue](https://about.gitlab.com/handbook/engineering/infrastructure/change-management/#feature-flags-and-the-change-management-process). Cross
link the issue here if it does.

View file

@ -14,7 +14,8 @@ Please link to the respective test case in the testcases project
- [ ] Ensure that no [transient bugs](https://about.gitlab.com/handbook/engineering/quality/issue-triage/#transient-bugs) are hidden accidentally due to the usage of `waits` and `reloads`.
- [ ] Verify the tags to ensure it runs on the desired test environments.
- [ ] If this MR has a dependency on another MR, such as a GitLab QA MR, specify the order in which the MRs should be merged.
- [ ] (If applicable) Create a follow-up issue to document [the special setup](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/running_tests_that_require_special_setup.html) necessary to run the test: ISSUE_LINK
- [ ] (If applicable) Create a follow-up issue to document [the special setup](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/running_tests_that_require_special_setup.html) necessary to run the test: ISSUE_LINK
- [ ] If the test requires an admin's personal access token, ensure that the test passes on your local with and without the `GITLAB_QA_ADMIN_ACCESS_TOKEN` provided.
<!-- Base labels. -->
/label ~"Quality" ~"QA" ~test

View file

@ -0,0 +1,45 @@
<script>
import { GlBadge } from '@gitlab/ui';
import { s__ } from '~/locale';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants';
const badge = {
[INSTANCE_TYPE]: {
variant: 'success',
text: s__('Runners|shared'),
},
[GROUP_TYPE]: {
variant: 'success',
text: s__('Runners|group'),
},
[PROJECT_TYPE]: {
variant: 'info',
text: s__('Runners|specific'),
},
};
export default {
components: {
GlBadge,
},
props: {
type: {
type: String,
required: true,
},
},
computed: {
variant() {
return badge[this.type]?.variant;
},
text() {
return badge[this.type]?.text;
},
},
};
</script>
<template>
<gl-badge v-if="text" :variant="variant" v-bind="$attrs">
{{ text }}
</gl-badge>
</template>

View file

@ -0,0 +1,11 @@
import { s__ } from '~/locale';
export const I18N_DETAILS_TITLE = s__('Runners|Runner #%{runner_id}');
export const RUNNER_ENTITY_TYPE = 'Ci::Runner';
// CiRunnerType
export const INSTANCE_TYPE = 'INSTANCE_TYPE';
export const GROUP_TYPE = 'GROUP_TYPE';
export const PROJECT_TYPE = 'PROJECT_TYPE';

View file

@ -0,0 +1,6 @@
query getRunner($id: CiRunnerID!) {
runner(id: $id) {
id
runnerType
}
}

View file

@ -1,3 +0,0 @@
import { s__ } from '~/locale';
export const I18N_TITLE = s__('Runners|Runner #%{runner_id}');

View file

@ -1,7 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import RunnerDetailsApp from './runner_details_app.vue';
export const initRunnerDetail = (selector = '#js-runner-detail') => {
Vue.use(VueApollo);
export const initRunnerDetail = (selector = '#js-runner-details') => {
const el = document.querySelector(selector);
if (!el) {
@ -10,8 +14,18 @@ export const initRunnerDetail = (selector = '#js-runner-detail') => {
const { runnerId } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
assumeImmutableResults: true,
},
),
});
return new Vue({
el,
apolloProvider,
render(h) {
return h(RunnerDetailsApp, {
props: {

View file

@ -1,9 +1,15 @@
<script>
import { I18N_TITLE } from './constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import RunnerTypeBadge from '../components/runner_type_badge.vue';
import { I18N_DETAILS_TITLE, RUNNER_ENTITY_TYPE } from '../constants';
import getRunnerQuery from '../graphql/get_runner.query.graphql';
export default {
components: {
RunnerTypeBadge,
},
i18n: {
I18N_TITLE,
I18N_DETAILS_TITLE,
},
props: {
runnerId: {
@ -11,10 +17,27 @@ export default {
required: true,
},
},
data() {
return {
runner: {},
};
},
apollo: {
runner: {
query: getRunnerQuery,
variables() {
return {
id: convertToGraphQLId(RUNNER_ENTITY_TYPE, this.runnerId),
};
},
},
},
};
</script>
<template>
<h2 class="page-title">
{{ sprintf($options.i18n.I18N_TITLE, { runner_id: runnerId }) }}
{{ sprintf($options.i18n.I18N_DETAILS_TITLE, { runner_id: runnerId }) }}
<runner-type-badge v-if="runner.runnerType" :type="runner.runnerType" />
</h2>
</template>

View file

@ -1,7 +1,7 @@
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/graphql_shared/fragments/user_availability.fragment.graphql"
query issueParticipants($fullPath: ID!, $iid: String!) {
query issueAssignees($fullPath: ID!, $iid: String!) {
workspace: project(fullPath: $fullPath) {
__typename
issuable: issue(iid: $iid) {

View file

@ -88,6 +88,9 @@ export default {
},
},
searchUsers: {
// TODO Remove error policy
// https://gitlab.com/gitlab-org/gitlab/-/issues/329750
errorPolicy: 'all',
query: searchUsers,
variables() {
return {
@ -97,10 +100,26 @@ export default {
};
},
update(data) {
return data.workspace?.users?.nodes.map(({ user }) => user) || [];
// TODO Remove null filter (BE fix required)
// https://gitlab.com/gitlab-org/gitlab/-/issues/329750
return data.workspace?.users?.nodes.filter((x) => x).map(({ user }) => user) || [];
},
debounce: ASSIGNEES_DEBOUNCE_DELAY,
error() {
error({ graphQLErrors }) {
// TODO This error suppression is temporary (BE fix required)
// https://gitlab.com/gitlab-org/gitlab/-/issues/329750
if (
graphQLErrors.length === 1 &&
graphQLErrors[0]?.message === 'Cannot return null for non-nullable field GroupMember.user'
) {
// eslint-disable-next-line no-console
console.error(
"Suppressing the error 'Cannot return null for non-nullable field GroupMember.user'. Please see https://gitlab.com/gitlab-org/gitlab/-/issues/329750",
);
this.isSearching = false;
return;
}
this.$emit('error');
this.isSearching = false;
},
@ -117,15 +136,20 @@ export default {
if (!this.participants) {
return [];
}
const mergedSearchResults = this.participants.reduce((acc, current) => {
if (
!acc.some((user) => current.username === user.username) &&
(current.name.includes(this.search) || current.username.includes(this.search))
) {
acc.push(current);
}
return acc;
}, this.searchUsers);
const filteredParticipants = this.participants.filter(
(user) => user.name.includes(this.search) || user.username.includes(this.search),
);
// TODO this de-duplication is temporary (BE fix required)
// https://gitlab.com/gitlab-org/gitlab/-/issues/327822
const mergedSearchResults = filteredParticipants
.concat(this.searchUsers)
.reduce(
(acc, current) => (acc.some((user) => current.id === user.id) ? acc : [...acc, current]),
[],
);
return this.moveCurrentUserToStart(mergedSearchResults);
},
isSearchEmpty() {

View file

@ -24,7 +24,7 @@ class Release < ApplicationRecord
before_create :set_released_at
validates :project, :tag, presence: true
validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, if: :should_validate_description_length?
validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, if: :description_changed?
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
validates :links, nested_attributes_duplicates: { scope: :release, child_attributes: %i[name url filepath] }
@ -102,11 +102,6 @@ class Release < ApplicationRecord
private
def should_validate_description_length?
description_changed? &&
::Feature.enabled?(:validate_release_description_length, project, default_enabled: :yaml)
end
def actual_sha
sha || actual_tag&.dereferenced_target
end

View file

@ -5,7 +5,7 @@
- add_to_breadcrumbs _('Runners'), admin_runners_path
- if Feature.enabled?(:runner_detailed_view_vue_ui, current_user, default_enabled: :yaml)
#js-runner-detail{ data: {runner_id: @runner.id} }
#js-runner-details{ data: {runner_id: @runner.id} }
- else
%h2.page-title
= s_('Runners|Runner #%{runner_id}' % { runner_id: @runner.id })

View file

@ -3,21 +3,30 @@ module LimitedCapacity
class JobTracker # rubocop:disable Scalability/IdempotentWorker
include Gitlab::Utils::StrongMemoize
LUA_REGISTER_SCRIPT = <<~EOS
local set_key, element, max_elements = KEYS[1], ARGV[1], ARGV[2]
if redis.call("scard", set_key) < tonumber(max_elements) then
redis.call("sadd", set_key, element)
return true
end
return false
EOS
def initialize(namespace)
@namespace = namespace
end
def register(jid)
_added, @count = with_redis_pipeline do |redis|
register_job_keys(redis, jid)
get_job_count(redis)
end
def register(jid, max_jids)
with_redis do |redis|
redis.eval(LUA_REGISTER_SCRIPT, keys: [counter_key], argv: [jid, max_jids])
end.present?
end
def remove(jid)
_removed, @count = with_redis_pipeline do |redis|
with_redis do |redis|
remove_job_keys(redis, jid)
get_job_count(redis)
end
end
@ -25,14 +34,13 @@ module LimitedCapacity
completed_jids = Gitlab::SidekiqStatus.completed_jids(running_jids)
return unless completed_jids.any?
_removed, @count = with_redis_pipeline do |redis|
with_redis do |redis|
remove_job_keys(redis, completed_jids)
get_job_count(redis)
end
end
def count
@count ||= with_redis { |redis| get_job_count(redis) }
with_redis { |redis| redis.scard(counter_key) }
end
def running_jids
@ -49,14 +57,6 @@ module LimitedCapacity
"worker:#{namespace.to_s.underscore}:running"
end
def get_job_count(redis)
redis.scard(counter_key)
end
def register_job_keys(redis, keys)
redis.sadd(counter_key, keys)
end
def remove_job_keys(redis, keys)
redis.srem(counter_key, keys)
end
@ -64,11 +64,5 @@ module LimitedCapacity
def with_redis(&block)
Gitlab::Redis::Queues.with(&block) # rubocop: disable CodeReuse/ActiveRecord
end
def with_redis_pipeline(&block)
with_redis do |redis|
redis.pipelined(&block)
end
end
end
end

View file

@ -55,26 +55,14 @@ module LimitedCapacity
def perform_with_capacity(*args)
worker = self.new
worker.remove_failed_jobs
worker.report_prometheus_metrics(*args)
required_jobs_count = worker.required_jobs_count(*args)
arguments = Array.new(required_jobs_count) { args }
arguments = Array.new(worker.max_running_jobs) { args }
self.bulk_perform_async(arguments) # rubocop:disable Scalability/BulkPerformWithContext
end
end
def perform(*args)
return unless has_capacity?
job_tracker.register(jid)
report_running_jobs_metrics
perform_work(*args)
rescue StandardError => exception
raise
ensure
job_tracker.remove(jid)
report_prometheus_metrics(*args)
re_enqueue(*args) unless exception
perform_registered(*args) if job_tracker.register(jid, max_running_jobs)
end
def perform_work(*args)
@ -89,44 +77,33 @@ module LimitedCapacity
raise NotImplementedError
end
def has_capacity?
remaining_capacity > 0
end
def remaining_capacity
[
max_running_jobs - running_jobs_count - self.class.queue_size,
0
].max
end
def has_work?(*args)
remaining_work_count(*args) > 0
end
def remove_failed_jobs
job_tracker.clean_up
end
def report_prometheus_metrics(*args)
report_running_jobs_metrics
remaining_work_gauge.set(prometheus_labels, remaining_work_count(*args))
max_running_jobs_gauge.set(prometheus_labels, max_running_jobs)
end
def report_running_jobs_metrics
running_jobs_gauge.set(prometheus_labels, running_jobs_count)
end
def required_jobs_count(*args)
[
remaining_work_count(*args),
remaining_capacity
].min
set_metric(:remaining_work_gauge, remaining_work_count(*args))
set_metric(:max_running_jobs_gauge, max_running_jobs)
end
private
def perform_registered(*args)
report_running_jobs_metrics
perform_work(*args)
rescue StandardError => exception
raise
ensure
job_tracker.remove(jid)
report_prometheus_metrics(*args)
re_enqueue(*args) unless exception
end
def report_running_jobs_metrics
set_metric(:running_jobs_gauge, running_jobs_count)
end
def running_jobs_count
job_tracker.count
end
@ -138,32 +115,21 @@ module LimitedCapacity
end
def re_enqueue(*args)
return unless has_capacity?
return unless has_work?(*args)
return unless remaining_work_count(*args) > 0
self.class.perform_async(*args)
end
def running_jobs_gauge
strong_memoize(:running_jobs_gauge) do
Gitlab::Metrics.gauge(:limited_capacity_worker_running_jobs, 'Number of running jobs')
def set_metric(name, value)
metrics = strong_memoize(:metrics) do
{
running_jobs_gauge: Gitlab::Metrics.gauge(:limited_capacity_worker_running_jobs, 'Number of running jobs'),
max_running_jobs_gauge: Gitlab::Metrics.gauge(:limited_capacity_worker_max_running_jobs, 'Maximum number of running jobs'),
remaining_work_gauge: Gitlab::Metrics.gauge(:limited_capacity_worker_remaining_work_count, 'Number of jobs waiting to be enqueued')
}
end
end
def max_running_jobs_gauge
strong_memoize(:max_running_jobs_gauge) do
Gitlab::Metrics.gauge(:limited_capacity_worker_max_running_jobs, 'Maximum number of running jobs')
end
end
def remaining_work_gauge
strong_memoize(:remaining_work_gauge) do
Gitlab::Metrics.gauge(:limited_capacity_worker_remaining_work_count, 'Number of jobs waiting to be enqueued')
end
end
def prometheus_labels
{ worker: self.class.name }
metrics[name].set({ worker: self.class.name }, value)
end
end
end

View file

@ -0,0 +1,5 @@
---
title: Validate release description length
merge_request: 60892
author:
type: changed

View file

@ -0,0 +1,5 @@
---
title: 'Github Importer: Add Cache to Pull Request Reviews importer'
merge_request: 60668
author:
type: changed

View file

@ -1,8 +0,0 @@
---
name: validate_release_description_length
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60380
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/329192
milestone: '13.12'
type: development
group: group::release
default_enabled: false

View file

@ -86,6 +86,7 @@ The generator takes three options:
- `--ee`: Indicates if the event is for EE.
- `--category=CATEGORY`: Indicates the `category` of the event.
- `--action=ACTION`: Indicates the `action` of the event.
- `--force`: Overwrites the existing event definition, if one already exists.
```shell
bundle exec rails generate gitlab:snowplow_event_definition --category Groups::EmailCampaignsController --action click

View file

@ -10460,6 +10460,30 @@ Status: `implemented`
Tiers: `premium`, `ultimate`
### `redis_hll_counters.epics_usage.g_project_management_users_awarding_epic_emoji_monthly`
Counts of MAU awarding emoji on epic
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210503011217_g_project_management_users_awarding_epic_emoji_monthly.yml)
Group: `group::product planning`
Status: `implemented`
Tiers: `premium`, `ultimate`
### `redis_hll_counters.epics_usage.g_project_management_users_awarding_epic_emoji_weekly`
Counts of WAU awarding emoji on epic
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210503011355_g_project_management_users_awarding_epic_emoji_weekly.yml)
Group: `group::product planning`
Status: `implemented`
Tiers: `premium`, `ultimate`
### `redis_hll_counters.epics_usage.g_project_management_users_creating_epic_notes_monthly`
Counts of MAU adding epic notes

View file

@ -15,3 +15,7 @@ following compliance tools are available:
- [License Compliance](license_compliance/index.md): Search your project's dependencies for their
licenses. This lets you determine if the licenses of your project's dependencies are compatible
with your project's license.
- [Compliance framework labels](../project/settings/index.md#compliance-frameworks): Label your projects that have unique compliance requirements.
- [Compliance pipelines](../project/settings/index.md#compliance-pipeline-configuration): Ensure that needed compliance jobs are always run for compliance-labeled projects.
- [Audit Events](../../administration/audit_events.md): Get visibility into individual actions that have taken place in your GitLab instance, group, or project.
- [Audit Reports](../../administration/audit_reports.md): Create and access reports based on the audit events that have occurred. Use pre-built GitLab reports or the API to build your own.

View file

@ -14,11 +14,12 @@ module Gitlab
class_option :ee, type: :boolean, optional: true, default: false, desc: 'Indicates if event is for ee'
class_option :category, type: :string, optional: false, desc: 'Category of the event'
class_option :action, type: :string, optional: false, desc: 'Action of the event'
class_option :force, type: :boolean, optional: true, default: false, desc: 'Overwrite existing definition'
def create_event_file
raise 'Event definition already exists' if definition_exists?
raise "Event definition already exists at #{file_path}" if definition_exists? && !force_definition_override?
template "event_definition.yml", file_path
template "event_definition.yml", file_path, force: force_definition_override?
end
def distributions
@ -41,6 +42,10 @@ module Gitlab
options[:ee]
end
def force_definition_override?
options[:force]
end
private
def definition_exists?

View file

@ -70,7 +70,7 @@ module Gitlab
end
def pull_request_reviews(repo_name, iid)
with_rate_limit { octokit.pull_request_reviews(repo_name, iid) }
each_object(:pull_request_reviews, repo_name, iid)
end
# Returns the details of a GitHub repository.

View file

@ -22,14 +22,18 @@ module Gitlab
:pull_requests_merged_by
end
def id_for_already_imported_cache(pr)
pr.number
def id_for_already_imported_cache(merge_request)
merge_request.id
end
def each_object_to_import
project.merge_requests.with_state(:merged).find_each do |merge_request|
next if already_imported?(merge_request)
pull_request = client.pull_request(project.import_source, merge_request.iid)
yield(pull_request)
mark_as_imported(merge_request)
end
end
end

View file

@ -22,17 +22,22 @@ module Gitlab
:pull_request_reviews
end
def id_for_already_imported_cache(review)
review.github_id
def id_for_already_imported_cache(merge_request)
merge_request.id
end
def each_object_to_import
project.merge_requests.find_each do |merge_request|
reviews = client.pull_request_reviews(project.import_source, merge_request.iid)
reviews.each do |review|
review.merge_request_id = merge_request.id
yield(review)
end
next if already_imported?(merge_request)
client
.pull_request_reviews(project.import_source, merge_request.iid)
.each do |review|
review.merge_request_id = merge_request.id
yield(review)
end
mark_as_imported(merge_request)
end
end
end

View file

@ -55,6 +55,14 @@
aggregation: daily
feature_flag: track_epics_activity
# emoji
- name: g_project_management_users_awarding_epic_emoji
category: epics_usage
redis_slot: project_management
aggregation: daily
feature_flag: track_epics_activity
# start date events
- name: g_project_management_users_setting_epic_start_date_as_fixed

View file

@ -0,0 +1,40 @@
import { GlBadge } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RunnerTypeBadge from '~/runner/components/runner_type_badge.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
describe('RunnerTypeBadge', () => {
let wrapper;
const findBadge = () => wrapper.findComponent(GlBadge);
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMount(RunnerTypeBadge, {
propsData: {
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
it.each`
type | text | variant
${INSTANCE_TYPE} | ${'shared'} | ${'success'}
${GROUP_TYPE} | ${'group'} | ${'success'}
${PROJECT_TYPE} | ${'specific'} | ${'info'}
`('displays $type runner with as "$text" with a $variant variant ', ({ type, text, variant }) => {
createComponent({ props: { type } });
expect(findBadge().text()).toBe(text);
expect(findBadge().props('variant')).toBe(variant);
});
it('does not display a badge when type is unknown', () => {
createComponent({ props: { type: 'AN_UNKNOWN_VALUE' } });
expect(findBadge().exists()).toBe(false);
});
});

View file

@ -1,29 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import RunnerDetailsApp from '~/runner/runner_details/runner_details_app.vue';
const mockRunnerId = '55';
describe('RunnerDetailsApp', () => {
let wrapper;
const createComponent = (props) => {
wrapper = shallowMount(RunnerDetailsApp, {
propsData: {
runnerId: mockRunnerId,
...props,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('displays the runner id', () => {
expect(wrapper.text()).toContain('Runner #55');
});
});

View file

@ -0,0 +1,71 @@
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import RunnerTypeBadge from '~/runner/components/runner_type_badge.vue';
import { INSTANCE_TYPE } from '~/runner/constants';
import getRunnerQuery from '~/runner/graphql/get_runner.query.graphql';
import RunnerDetailsApp from '~/runner/runner_details/runner_details_app.vue';
const mockRunnerId = '55';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('RunnerDetailsApp', () => {
let wrapper;
let mockRunnerQuery;
const findRunnerTypeBadge = () => wrapper.findComponent(RunnerTypeBadge);
const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
const handlers = [[getRunnerQuery, mockRunnerQuery]];
wrapper = mountFn(RunnerDetailsApp, {
localVue,
apolloProvider: createMockApollo(handlers),
propsData: {
runnerId: mockRunnerId,
...props,
},
});
return waitForPromises();
};
beforeEach(async () => {
mockRunnerQuery = jest.fn().mockResolvedValue({
data: {
runner: {
id: `gid://gitlab/Ci::Runner/${mockRunnerId}`,
runnerType: INSTANCE_TYPE,
__typename: 'CiRunner',
},
},
});
});
afterEach(() => {
mockRunnerQuery.mockReset();
wrapper.destroy();
});
it('expect GraphQL ID to be requested', async () => {
await createComponentWithApollo();
expect(mockRunnerQuery).toHaveBeenCalledWith({ id: `gid://gitlab/Ci::Runner/${mockRunnerId}` });
});
it('displays the runner id', async () => {
await createComponentWithApollo();
expect(wrapper.text()).toContain('Runner #55');
});
it('displays the runner type', async () => {
await createComponentWithApollo({ mountFn: mount });
expect(findRunnerTypeBadge().text()).toBe('shared');
});
});

View file

@ -380,6 +380,25 @@ export const subscriptionNullResponse = {
},
};
const mockUser1 = {
id: 'gid://gitlab/User/1',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
name: 'Administrator',
username: 'root',
webUrl: '/root',
status: null,
};
const mockUser2 = {
id: 'gid://gitlab/User/4',
avatarUrl: '/avatar2',
name: 'rookie',
username: 'rookie',
webUrl: 'rookie',
status: null,
};
export const searchResponse = {
data: {
workspace: {
@ -387,24 +406,10 @@ export const searchResponse = {
users: {
nodes: [
{
user: {
id: '1',
avatarUrl: '/avatar',
name: 'root',
username: 'root',
webUrl: 'root',
status: null,
},
user: mockUser1,
},
{
user: {
id: '2',
avatarUrl: '/avatar2',
name: 'rookie',
username: 'rookie',
webUrl: 'rookie',
status: null,
},
user: mockUser2,
},
],
},
@ -418,27 +423,13 @@ export const projectMembersResponse = {
__typename: 'Project',
users: {
nodes: [
{
user: {
id: 'gid://gitlab/User/1',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
name: 'Administrator',
username: 'root',
webUrl: '/root',
status: null,
},
},
{
user: {
id: '2',
avatarUrl: '/avatar2',
name: 'rookie',
username: 'rookie',
webUrl: 'rookie',
status: null,
},
},
// Remove nulls https://gitlab.com/gitlab-org/gitlab/-/issues/329750
null,
null,
// Remove duplicated entry https://gitlab.com/gitlab-org/gitlab/-/issues/327822
mockUser1,
mockUser1,
mockUser2,
{
user: {
id: 'gid://gitlab/User/2',
@ -468,15 +459,9 @@ export const participantsQueryResponse = {
iid: '1',
participants: {
nodes: [
{
id: 'gid://gitlab/User/1',
avatarUrl:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
name: 'Administrator',
username: 'root',
webUrl: '/root',
status: null,
},
// Remove duplicated entry https://gitlab.com/gitlab-org/gitlab/-/issues/327822
mockUser1,
mockUser1,
{
id: 'gid://gitlab/User/2',
avatarUrl:

View file

@ -32,6 +32,30 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator do
expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event)
end
context 'event definition already exists' do
before do
stub_const('Gitlab::VERSION', '12.11.0-pre')
described_class.new([], generator_options).invoke_all
end
it 'overwrites event definition --force flag set to true' do
sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event.yml'))).load_raw!
stub_const('Gitlab::VERSION', '13.11.0-pre')
described_class.new([], generator_options.merge('force' => true)).invoke_all
event_definition_path = File.join(ce_temp_dir, 'groups__email_campaigns_controller_click.yml')
event_data = ::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!
expect(event_data).to eq(sample_event)
end
it 'raises error when --force flag set to false' do
expect { described_class.new([], generator_options.merge('force' => false)).invoke_all }
.to raise_error(StandardError, /Event definition already exists at/)
end
end
it 'creates EE event definition file using the template' do
sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event_ee.yml'))).load_raw!

View file

@ -32,8 +32,9 @@ RSpec.describe Gitlab::GithubImport::Client do
it 'returns the pull request reviews' do
client = described_class.new('foo')
expect(client.octokit).to receive(:pull_request_reviews).with('foo/bar', 999)
expect(client).to receive(:with_rate_limit).and_yield
expect(client)
.to receive(:each_object)
.with(:pull_request_reviews, 'foo/bar', 999)
client.pull_request_reviews('foo/bar', 999)
end

View file

@ -23,12 +23,11 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
end
describe '#id_for_already_imported_cache' do
it { expect(subject.id_for_already_imported_cache(double(number: 1))).to eq(1) }
it { expect(subject.id_for_already_imported_cache(double(id: 1))).to eq(1) }
end
describe '#each_object_to_import' do
describe '#each_object_to_import', :clean_gitlab_redis_cache do
it 'fetchs the merged pull requests data' do
pull_request = double
create(
:merged_merge_request,
iid: 999,
@ -36,12 +35,18 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsMergedByImporter do
target_project: project
)
pull_request = double
allow(client)
.to receive(:pull_request)
.exactly(:once) # ensure to be cached on the second call
.with('http://somegithub.com', 999)
.and_return(pull_request)
expect { |b| subject.each_object_to_import(&b) }.to yield_with_args(pull_request)
expect { |b| subject.each_object_to_import(&b) }
.to yield_with_args(pull_request)
subject.each_object_to_import {}
end
end
end

View file

@ -23,12 +23,18 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsReviewsImporter do
end
describe '#id_for_already_imported_cache' do
it { expect(subject.id_for_already_imported_cache(double(github_id: 1))).to eq(1) }
it { expect(subject.id_for_already_imported_cache(double(id: 1))).to eq(1) }
end
describe '#each_object_to_import' do
describe '#each_object_to_import', :clean_gitlab_redis_cache do
it 'fetchs the merged pull requests data' do
merge_request = create(:merge_request, source_project: project)
merge_request = create(
:merged_merge_request,
iid: 999,
source_project: project,
target_project: project
)
review = double
expect(review)
@ -37,10 +43,14 @@ RSpec.describe Gitlab::GithubImport::Importer::PullRequestsReviewsImporter do
allow(client)
.to receive(:pull_request_reviews)
.exactly(:once) # ensure to be cached on the second call
.with('github/repo', merge_request.iid)
.and_return([review])
expect { |b| subject.each_object_to_import(&b) }.to yield_with_args(review)
expect { |b| subject.each_object_to_import(&b) }
.to yield_with_args(review)
subject.each_object_to_import {}
end
end
end

View file

@ -48,18 +48,6 @@ RSpec.describe Release do
expect(release.errors.full_messages)
.to include("Description is too long (maximum is #{Gitlab::Database::MAX_TEXT_SIZE_LIMIT} characters)")
end
context 'when validate_release_description_length feature flag is disabled' do
before do
stub_feature_flags(validate_release_description_length: false)
end
it 'does not create a validation error' do
release.validate
expect(release.errors.full_messages).to be_empty
end
end
end
context 'when a release is tied to a milestone for another project' do

View file

@ -71,7 +71,11 @@ module SnowplowHelpers
# expect_no_snowplow_event
# end
# end
def expect_no_snowplow_event
expect(Gitlab::Tracking).not_to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
def expect_no_snowplow_event(category: nil, action: nil, **kwargs)
if category && action
expect(Gitlab::Tracking).not_to have_received(:event).with(category, action, **kwargs) # rubocop:disable RSpec/ExpectGitlabTracking
else
expect(Gitlab::Tracking).not_to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
end
end
end

View file

@ -7,30 +7,30 @@ RSpec.describe LimitedCapacity::JobTracker, :clean_gitlab_redis_queues do
described_class.new('namespace')
end
let(:max_jids) { 10 }
describe '#register' do
it 'adds jid to the set' do
job_tracker.register('a-job-id')
expect(job_tracker.register('a-job-id', max_jids)). to be true
expect(job_tracker.running_jids).to contain_exactly('a-job-id')
end
it 'updates the counter' do
expect { job_tracker.register('a-job-id') }
.to change { job_tracker.count }
.from(0)
.to(1)
end
it 'returns false if the jid was not added' do
max_jids = 2
%w[jid1 jid2].each do |jid|
expect(job_tracker.register(jid, max_jids)).to be true
end
it 'does it in only one Redis call' do
expect(job_tracker).to receive(:with_redis).once.and_call_original
job_tracker.register('a-job-id')
expect(job_tracker.register('jid3', max_jids)).to be false
expect(job_tracker.running_jids).to contain_exactly(*%w[jid1 jid2])
end
end
describe '#remove' do
before do
job_tracker.register(%w[a-job-id other-job-id])
%w[a-job-id other-job-id].each do |jid|
job_tracker.register(jid, max_jids)
end
end
it 'removes jid from the set' do
@ -38,24 +38,11 @@ RSpec.describe LimitedCapacity::JobTracker, :clean_gitlab_redis_queues do
expect(job_tracker.running_jids).to contain_exactly('a-job-id')
end
it 'updates the counter' do
expect { job_tracker.remove('other-job-id') }
.to change { job_tracker.count }
.from(2)
.to(1)
end
it 'does it in only one Redis call' do
expect(job_tracker).to receive(:with_redis).once.and_call_original
job_tracker.remove('other-job-id')
end
end
describe '#clean_up' do
before do
job_tracker.register('a-job-id')
job_tracker.register('a-job-id', max_jids)
end
context 'with running jobs' do
@ -83,13 +70,6 @@ RSpec.describe LimitedCapacity::JobTracker, :clean_gitlab_redis_queues do
.to change { job_tracker.running_jids.include?('a-job-id') }
end
it 'updates the counter' do
expect { job_tracker.clean_up }
.to change { job_tracker.count }
.from(1)
.to(0)
end
it 'gets the job ids, removes them, and updates the counter with only two Redis calls' do
expect(job_tracker).to receive(:with_redis).twice.and_call_original

View file

@ -44,40 +44,22 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f
describe '.perform_with_capacity' do
subject(:perform_with_capacity) { worker_class.perform_with_capacity(:arg) }
let(:max_running_jobs) { 3 }
before do
expect_next_instance_of(worker_class) do |instance|
expect(instance).to receive(:remove_failed_jobs)
expect(instance).to receive(:report_prometheus_metrics)
allow(instance).to receive(:remaining_work_count).and_return(remaining_work_count)
allow(instance).to receive(:remaining_capacity).and_return(remaining_capacity)
allow(instance).to receive(:max_running_jobs).and_return(max_running_jobs)
end
end
context 'when capacity is larger than work' do
let(:remaining_work_count) { 2 }
let(:remaining_capacity) { 3 }
it 'enqueues jobs' do
expect(worker_class)
.to receive(:bulk_perform_async)
.with([[:arg], [:arg], [:arg]])
it 'enqueues jobs for remaining work' do
expect(worker_class)
.to receive(:bulk_perform_async)
.with([[:arg], [:arg]])
perform_with_capacity
end
end
context 'when capacity is lower than work' do
let(:remaining_work_count) { 5 }
let(:remaining_capacity) { 3 }
it 'enqueues jobs for remaining work' do
expect(worker_class)
.to receive(:bulk_perform_async)
.with([[:arg], [:arg], [:arg]])
perform_with_capacity
end
perform_with_capacity
end
end
@ -104,21 +86,6 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f
perform
end
it 'registers itself in the running set' do
allow(worker).to receive(:perform_work)
expect(job_tracker).to receive(:register).with('my-jid')
perform
end
it 'removes itself from the running set' do
expect(job_tracker).to receive(:remove).with('my-jid')
allow(worker).to receive(:perform_work)
perform
end
it 'reports prometheus metrics' do
allow(worker).to receive(:perform_work)
expect(worker).to receive(:report_prometheus_metrics).once.and_call_original
@ -126,12 +93,20 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f
perform
end
it 'updates the running set' do
expect(job_tracker.running_jids).to be_empty
allow(worker).to receive(:perform_work)
perform
expect(job_tracker.running_jids).to be_empty
end
end
context 'with capacity and without work' do
before do
allow(worker).to receive(:max_running_jobs).and_return(10)
allow(worker).to receive(:running_jobs_count).and_return(0)
allow(worker).to receive(:remaining_work_count).and_return(0)
allow(worker).to receive(:perform_work)
end
@ -146,7 +121,7 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f
context 'without capacity' do
before do
allow(worker).to receive(:max_running_jobs).and_return(10)
allow(worker).to receive(:running_jobs_count).and_return(15)
allow(job_tracker).to receive(:register).and_return(false)
allow(worker).to receive(:remaining_work_count).and_return(10)
end
@ -161,27 +136,14 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f
perform
end
it 'does not register in the running set' do
expect(job_tracker).not_to receive(:register)
perform
end
it 'removes itself from the running set' do
expect(job_tracker).to receive(:remove).with('my-jid')
perform
end
it 'reports prometheus metrics' do
expect(worker).to receive(:report_prometheus_metrics)
perform
end
end
context 'when perform_work fails' do
before do
allow(worker).to receive(:max_running_jobs).and_return(10)
allow(job_tracker).to receive(:register).and_return(true)
end
it 'does not re-enqueue itself' do
expect(worker).not_to receive(:re_enqueue)
@ -189,7 +151,7 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f
end
it 'removes itself from the running set' do
expect(job_tracker).to receive(:remove)
expect(job_tracker).to receive(:remove).with('my-jid')
expect { perform }.to raise_error(NotImplementedError)
end
@ -202,65 +164,14 @@ RSpec.describe LimitedCapacity::Worker, :clean_gitlab_redis_queues, :aggregate_f
end
end
describe '#remaining_capacity' do
subject(:remaining_capacity) { worker.remaining_capacity }
before do
expect(worker).to receive(:max_running_jobs).and_return(max_capacity)
end
context 'when changing the capacity to a lower value' do
let(:max_capacity) { -1 }
it { expect(remaining_capacity).to eq(0) }
end
context 'when registering new jobs' do
let(:max_capacity) { 2 }
before do
job_tracker.register('a-job-id')
end
it { expect(remaining_capacity).to eq(1) }
end
context 'with jobs in the queue' do
let(:max_capacity) { 2 }
before do
expect(worker_class).to receive(:queue_size).and_return(1)
end
it { expect(remaining_capacity).to eq(1) }
end
context 'with both running jobs and queued jobs' do
let(:max_capacity) { 10 }
before do
expect(worker_class).to receive(:queue_size).and_return(5)
expect(worker).to receive(:running_jobs_count).and_return(3)
end
it { expect(remaining_capacity).to eq(2) }
end
end
describe '#remove_failed_jobs' do
subject(:remove_failed_jobs) { worker.remove_failed_jobs }
before do
job_tracker.register('a-job-id')
allow(worker).to receive(:max_running_jobs).and_return(2)
it 'removes failed jobs' do
job_tracker.register('a-job-id', 10)
expect(job_tracker).to receive(:clean_up).and_call_original
end
context 'with failed jobs' do
it 'update the available capacity' do
expect { remove_failed_jobs }.to change { worker.remaining_capacity }.by(1)
end
expect { remove_failed_jobs }.to change { job_tracker.running_jids.size }.by(-1)
end
end