Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo.yml8
-rw-r--r--app/assets/javascripts/ajax_loading_spinner.js35
-rw-r--r--app/assets/javascripts/branches/ajax_loading_spinner.js31
-rw-r--r--app/assets/javascripts/packages/details/components/additional_metadata.vue2
-rw-r--r--app/assets/javascripts/packages/details/components/package_history.vue24
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js2
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/registry/details_row.vue (renamed from app/assets/javascripts/registry/shared/components/details_row.vue)0
-rw-r--r--app/assets/javascripts/vue_shared/components/registry/history_item.vue (renamed from app/assets/javascripts/packages/details/components/history_element.vue)7
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/controllers/concerns/send_file_upload.rb3
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/models/concerns/issuable.rb6
-rw-r--r--app/services/issues/base_service.rb4
-rw-r--r--app/services/issues/create_service.rb3
-rw-r--r--app/views/projects/branches/_branch.html.haml14
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar_todo.html.haml2
-rw-r--r--app/workers/all_queues.yml8
-rw-r--r--app/workers/issue_placement_worker.rb36
-rw-r--r--app/workers/new_issue_worker.rb4
-rw-r--r--changelogs/unreleased/202267-migrate-spinner-for-app-assets-javascripts-vue_merge_request_widge.yml5
-rw-r--r--changelogs/unreleased/202590-migrate-spinner-for-app-views-shared-issuable.yml5
-rw-r--r--changelogs/unreleased/205578-add-package-count-to-usage-data-28days.yml5
-rw-r--r--changelogs/unreleased/ajk-relative-positioning-async-move-to-end.yml5
-rw-r--r--changelogs/unreleased/dz-improve-new-issue-highlight.yml5
-rw-r--r--changelogs/unreleased/leipert-ajax-spinner-icons.yml5
-rw-r--r--changelogs/unreleased/rails-save-bang-21.yml5
-rw-r--r--config/feature_flags/development/security_on_demand_scans_site_validation.yml (renamed from config/feature_flags/development/security-on-demand-scans-site-validation.yml)2
-rw-r--r--config/initializers/carrierwave_patch.rb6
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/migrate/20200901203055_add_id_created_at_index_to_packages.rb19
-rw-r--r--db/schema_migrations/202009012030551
-rw-r--r--db/structure.sql2
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql113
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json318
-rw-r--r--doc/api/graphql/reference/index.md15
-rw-r--r--doc/development/geo/framework.md2
-rw-r--r--doc/development/i18n/externalization.md85
-rw-r--r--doc/install/installation.md3
-rw-r--r--doc/user/packages/container_registry/index.md2
-rw-r--r--lib/gitlab/usage_data.rb1
-rw-r--r--locale/gitlab.pot39
-rw-r--r--package.json2
-rw-r--r--spec/controllers/concerns/send_file_upload_spec.rb17
-rw-r--r--spec/factories/usage_data.rb1
-rw-r--r--spec/fixtures/api/schemas/issue.json2
-rw-r--r--spec/frontend/ajax_loading_spinner_spec.js59
-rw-r--r--spec/frontend/branches/ajax_loading_spinner_spec.js32
-rw-r--r--spec/frontend/fixtures/static/ajax_loading_spinner.html3
-rw-r--r--spec/frontend/packages/details/components/additional_metadata_spec.js2
-rw-r--r--spec/frontend/packages/details/components/package_history_spec.js6
-rw-r--r--spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/registry/__snapshots__/history_item_spec.js.snap (renamed from spec/frontend/packages/details/components/__snapshots__/history_element_spec.js.snap)8
-rw-r--r--spec/frontend/vue_shared/components/registry/details_row_spec.js (renamed from spec/frontend/registry/shared/components/details_row_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/registry/history_item_spec.js (renamed from spec/frontend/packages/details/components/history_element_spec.js)14
-rw-r--r--spec/initializers/carrierwave_patch_spec.rb12
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb3
-rw-r--r--spec/models/concerns/issuable_spec.rb16
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb10
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb52
-rw-r--r--spec/models/project_services/packagist_service_spec.rb2
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb22
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb10
-rw-r--r--spec/services/issues/create_service_spec.rb31
-rw-r--r--spec/services/issues/reorder_service_spec.rb34
-rw-r--r--spec/workers/issue_placement_worker_spec.rb69
-rw-r--r--spec/workers/new_issue_worker_spec.rb8
-rw-r--r--yarn.lock74
71 files changed, 1038 insertions, 308 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 9c94cb46e74..e85eb1bfd2d 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -729,8 +729,6 @@ Rails/SaveBang:
- 'ee/spec/models/merge_train_spec.rb'
- 'spec/models/packages/package_spec.rb'
- 'ee/spec/models/project_ci_cd_setting_spec.rb'
- - 'ee/spec/models/project_services/github_service_spec.rb'
- - 'ee/spec/models/project_services/jenkins_service_spec.rb'
- 'ee/spec/models/project_spec.rb'
- 'ee/spec/models/protected_environment_spec.rb'
- 'ee/spec/models/repository_spec.rb'
@@ -1113,12 +1111,6 @@ Rails/SaveBang:
- 'spec/models/pages_domain_spec.rb'
- 'spec/models/project_auto_devops_spec.rb'
- 'spec/models/project_feature_spec.rb'
- - 'spec/models/project_services/bamboo_service_spec.rb'
- - 'spec/models/project_services/buildkite_service_spec.rb'
- - 'spec/models/project_services/jira_service_spec.rb'
- - 'spec/models/project_services/packagist_service_spec.rb'
- - 'spec/models/project_services/pipelines_email_service_spec.rb'
- - 'spec/models/project_services/teamcity_service_spec.rb'
- 'spec/models/project_spec.rb'
- 'spec/models/project_team_spec.rb'
- 'spec/models/protectable_dropdown_spec.rb'
diff --git a/app/assets/javascripts/ajax_loading_spinner.js b/app/assets/javascripts/ajax_loading_spinner.js
deleted file mode 100644
index 1c95bc5081c..00000000000
--- a/app/assets/javascripts/ajax_loading_spinner.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import $ from 'jquery';
-
-export default class AjaxLoadingSpinner {
- static init() {
- const $elements = $('.js-ajax-loading-spinner');
-
- $elements.on('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
- $elements.on('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
- }
-
- static ajaxBeforeSend(e) {
- e.target.setAttribute('disabled', '');
- const iconElement = e.target.querySelector('i');
- // get first fa- icon
- const originalIcon = iconElement.className.match(/(fa-)([^\s]+)/g)[0];
- iconElement.dataset.icon = originalIcon;
- AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
- $(e.target).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
- }
-
- static ajaxComplete(e) {
- e.target.removeAttribute('disabled');
- const iconElement = e.target.querySelector('i');
- AjaxLoadingSpinner.toggleLoadingIcon(iconElement);
- $(e.target).off('ajax:complete', AjaxLoadingSpinner.ajaxComplete);
- }
-
- static toggleLoadingIcon(iconElement) {
- const { classList } = iconElement;
- classList.toggle(iconElement.dataset.icon);
- classList.toggle('gl-spinner');
- classList.toggle('gl-spinner-orange');
- classList.toggle('gl-spinner-sm');
- }
-}
diff --git a/app/assets/javascripts/branches/ajax_loading_spinner.js b/app/assets/javascripts/branches/ajax_loading_spinner.js
new file mode 100644
index 00000000000..79f4f919f3d
--- /dev/null
+++ b/app/assets/javascripts/branches/ajax_loading_spinner.js
@@ -0,0 +1,31 @@
+import $ from 'jquery';
+
+export default class AjaxLoadingSpinner {
+ static init() {
+ const $elements = $('.js-ajax-loading-spinner');
+ $elements.on('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
+ }
+
+ static ajaxBeforeSend(e) {
+ const button = e.target;
+ const newButton = document.createElement('button');
+ newButton.classList.add('btn', 'btn-default', 'disabled', 'gl-button');
+ newButton.setAttribute('disabled', 'disabled');
+
+ const spinner = document.createElement('span');
+ spinner.classList.add('align-text-bottom', 'gl-spinner', 'gl-spinner-sm', 'gl-spinner-orange');
+ newButton.appendChild(spinner);
+
+ button.classList.add('hidden');
+ button.parentNode.insertBefore(newButton, button.nextSibling);
+
+ $(button).one('ajax:error', () => {
+ newButton.remove();
+ button.classList.remove('hidden');
+ });
+
+ $(button).one('ajax:success', () => {
+ $(button).off('ajax:beforeSend', AjaxLoadingSpinner.ajaxBeforeSend);
+ });
+ }
+}
diff --git a/app/assets/javascripts/packages/details/components/additional_metadata.vue b/app/assets/javascripts/packages/details/components/additional_metadata.vue
index a3de6dd46c7..76e0976ac05 100644
--- a/app/assets/javascripts/packages/details/components/additional_metadata.vue
+++ b/app/assets/javascripts/packages/details/components/additional_metadata.vue
@@ -1,7 +1,7 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
-import DetailsRow from '~/registry/shared/components/details_row.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
import { generateConanRecipe } from '../utils';
import { PackageType } from '../../shared/constants';
diff --git a/app/assets/javascripts/packages/details/components/package_history.vue b/app/assets/javascripts/packages/details/components/package_history.vue
index 96ce106884d..413ab1d15cb 100644
--- a/app/assets/javascripts/packages/details/components/package_history.vue
+++ b/app/assets/javascripts/packages/details/components/package_history.vue
@@ -2,7 +2,7 @@
import { GlLink, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import HistoryElement from './history_element.vue';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
export default {
name: 'PackageHistory',
@@ -16,7 +16,7 @@ export default {
components: {
GlLink,
GlSprintf,
- HistoryElement,
+ HistoryItem,
TimeAgoTooltip,
},
props: {
@@ -46,7 +46,7 @@ export default {
<div class="issuable-discussion">
<h3 class="gl-font-lg" data-testid="title">{{ __('History') }}</h3>
<ul class="timeline main-notes-list notes gl-mb-4" data-testid="timeline">
- <history-element icon="clock" data-testid="created-on">
+ <history-item icon="clock" data-testid="created-on">
<gl-sprintf :message="$options.i18n.createdOn">
<template #name>
<strong>{{ packageEntity.name }}</strong>
@@ -58,8 +58,8 @@ export default {
<time-ago-tooltip :time="packageEntity.created_at" />
</template>
</gl-sprintf>
- </history-element>
- <history-element icon="pencil" data-testid="updated-at">
+ </history-item>
+ <history-item icon="pencil" data-testid="updated-at">
<gl-sprintf :message="$options.i18n.updatedAtText">
<template #name>
<strong>{{ packageEntity.name }}</strong>
@@ -71,9 +71,9 @@ export default {
<time-ago-tooltip :time="packageEntity.updated_at" />
</template>
</gl-sprintf>
- </history-element>
+ </history-item>
<template v-if="packagePipeline">
- <history-element icon="commit" data-testid="commit">
+ <history-item icon="commit" data-testid="commit">
<gl-sprintf :message="$options.i18n.commitText">
<template #link>
<gl-link :href="packagePipeline.project.commit_url">{{
@@ -84,8 +84,8 @@ export default {
<strong>{{ packagePipeline.ref }}</strong>
</template>
</gl-sprintf>
- </history-element>
- <history-element icon="pipeline" data-testid="pipeline">
+ </history-item>
+ <history-item icon="pipeline" data-testid="pipeline">
<gl-sprintf :message="$options.i18n.pipelineText">
<template #link>
<gl-link :href="packagePipeline.project.pipeline_url"
@@ -97,9 +97,9 @@ export default {
</template>
<template #author>{{ packagePipeline.user.name }}</template>
</gl-sprintf>
- </history-element>
+ </history-item>
</template>
- <history-element icon="package" data-testid="published">
+ <history-item icon="package" data-testid="published">
<gl-sprintf :message="$options.i18n.publishText">
<template #project>
<strong>{{ projectName }}</strong>
@@ -108,7 +108,7 @@ export default {
<time-ago-tooltip :time="packageEntity.created_at" />
</template>
</gl-sprintf>
- </history-element>
+ </history-item>
</ul>
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index 37e8c75f299..623d0a10606 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -1,4 +1,4 @@
-import AjaxLoadingSpinner from '~/ajax_loading_spinner';
+import AjaxLoadingSpinner from '~/branches/ajax_loading_spinner';
import DeleteModal from '~/branches/branches_delete_modal';
import initDiverganceGraph from '~/branches/divergence_graph';
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue
index a4c497d0f59..661213733ac 100644
--- a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue
+++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue
@@ -7,7 +7,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import DeleteButton from '../delete_button.vue';
-import DetailsRow from '~/registry/shared/components/details_row.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
import {
REMOVE_TAG_BUTTON_TITLE,
DIGEST_LABEL,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue
index 54e1e9e9bc5..506d566cf70 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue
@@ -1,5 +1,6 @@
<script>
/* eslint-disable vue/no-v-html */
+import { GlLoadingIcon } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import statusCodes from '~/lib/utils/http_status';
import { bytesToMiB } from '~/lib/utils/number_utils';
@@ -11,6 +12,7 @@ export default {
name: 'MemoryUsage',
components: {
MemoryGraph,
+ GlLoadingIcon,
},
props: {
metricsUrl: {
@@ -156,8 +158,9 @@ export default {
<template>
<div class="mr-info-list clearfix mr-memory-usage js-mr-memory-usage">
<p v-if="shouldShowLoading" class="usage-info js-usage-info usage-info-loading">
- <i class="fa fa-spinner fa-spin usage-info-load-spinner" aria-hidden="true"> </i
- >{{ s__('mrWidget|Loading deployment statistics') }}
+ <gl-loading-icon class="usage-info-load-spinner" />{{
+ s__('mrWidget|Loading deployment statistics')
+ }}
</p>
<p
v-if="shouldShowMemoryGraph"
diff --git a/app/assets/javascripts/registry/shared/components/details_row.vue b/app/assets/javascripts/vue_shared/components/registry/details_row.vue
index 2e245fadead..2e245fadead 100644
--- a/app/assets/javascripts/registry/shared/components/details_row.vue
+++ b/app/assets/javascripts/vue_shared/components/registry/details_row.vue
diff --git a/app/assets/javascripts/packages/details/components/history_element.vue b/app/assets/javascripts/vue_shared/components/registry/history_item.vue
index 8a51c1528cf..a60b630b207 100644
--- a/app/assets/javascripts/packages/details/components/history_element.vue
+++ b/app/assets/javascripts/vue_shared/components/registry/history_item.vue
@@ -3,12 +3,11 @@ import { GlIcon } from '@gitlab/ui';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
export default {
- name: 'HistoryElement',
+ name: 'HistoryItem',
components: {
GlIcon,
TimelineEntryItem,
},
-
props: {
icon: {
type: String,
@@ -29,7 +28,9 @@ export default {
<slot></slot>
</span>
</div>
- <div class="note-body"></div>
+ <div class="note-body">
+ <slot name="body"></slot>
+ </div>
</div>
</timeline-entry-item>
</template>
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6226685d93d..2e3e725712b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -919,12 +919,12 @@
}
.issuable-todo-btn {
- .fa-spinner {
+ .gl-spinner {
display: none;
}
&.is-loading {
- .fa-spinner {
+ .gl-spinner {
display: inline-block;
}
diff --git a/app/controllers/concerns/send_file_upload.rb b/app/controllers/concerns/send_file_upload.rb
index d21d96fd527..2f06cd84ee5 100644
--- a/app/controllers/concerns/send_file_upload.rb
+++ b/app/controllers/concerns/send_file_upload.rb
@@ -55,8 +55,7 @@ module SendFileUpload
def image_scaling_request?(file_upload)
avatar_safe_for_scaling?(file_upload) &&
scaling_allowed_by_feature_flags?(file_upload) &&
- valid_image_scaling_width? &&
- current_user
+ valid_image_scaling_width?
end
def avatar_safe_for_scaling?(file_upload)
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 55170cbfa6b..e8ea39d7ffc 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -4,7 +4,7 @@ module IssuesHelper
def issue_css_classes(issue)
classes = ["issue"]
classes << "closed" if issue.closed?
- classes << "today" if issue.today?
+ classes << "today" if issue.new?
classes << "user-can-drag" if @sort == 'relative_position'
classes.join(' ')
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 46cd9649c2d..a6986029f0d 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -385,8 +385,12 @@ module Issuable
Date.today == created_at.to_date
end
+ def created_hours_ago
+ (Time.now.utc.to_i - created_at.utc.to_i) / 3600
+ end
+
def new?
- today? && created_at == updated_at
+ created_hours_ago < 24
end
def open?
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index cf1ef6a9710..f88fb806b97 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -17,8 +17,6 @@ module Issues
Issues::CloseService
end
- private
-
NO_REBALANCING_NEEDED = ((RelativePositioning::MIN_POSITION * 0.9999)..(RelativePositioning::MAX_POSITION * 0.9999)).freeze
def rebalance_if_needed(issue)
@@ -32,6 +30,8 @@ module Issues
IssueRebalancingWorker.perform_async(nil, issue.project_id)
end
+ private
+
def create_assignee_note(issue, old_assignees)
SystemNoteService.change_issuable_assignees(
issue, issue.project, current_user, old_assignees)
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index d9209191c1c..297a6b2ecdc 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -16,12 +16,12 @@ module Issues
def before_create(issue)
spam_check(issue, current_user, action: :create)
- issue.move_to_end
# current_user (defined in BaseService) is not available within run_after_commit block
user = current_user
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id)
+ IssuePlacementWorker.perform_async(issue.id)
end
end
@@ -30,7 +30,6 @@ module Issues
user_agent_detail_service.create
resolve_discussions_with_issue(issuable)
delete_milestone_total_issue_counter_cache(issuable.milestone)
- rebalance_if_needed(issuable)
super
end
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index ed7dbdeae93..020a4361203 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -50,25 +50,25 @@
- if can?(current_user, :push_code, @project)
- if branch.name == @project.repository.root_ref
- %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
+ %button{ class: "btn btn-remove remove-row has-tooltip disabled",
disabled: true,
title: s_('Branches|The default branch cannot be deleted') }
- = icon("trash-o")
+ = sprite_icon("remove")
- elsif protected_branch?(@project, branch)
- if can?(current_user, :push_to_delete_protected_branch, @project)
- %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
+ %button{ class: "btn btn-remove remove-row has-tooltip",
title: s_('Branches|Delete protected branch'),
data: { toggle: "modal",
target: "#modal-delete-branch",
delete_path: project_branch_path(@project, branch.name),
branch_name: branch.name,
is_merged: ("true" if merged) } }
- = icon("trash-o")
+ = sprite_icon("remove")
- else
- %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip disabled",
+ %button{ class: "btn btn-remove remove-row has-tooltip disabled",
disabled: true,
title: s_('Branches|Only a project maintainer or owner can delete a protected branch') }
- = icon("trash-o")
+ = sprite_icon("remove")
- else
= link_to project_branch_path(@project, branch.name),
class: "btn btn-remove remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip",
@@ -77,4 +77,4 @@
data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
remote: true,
'aria-label' => s_('Branches|Delete branch') do
- = icon("trash-o")
+ = sprite_icon("remove")
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index c4e8a2d51ee..b336ab3eb00 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -63,7 +63,7 @@
// Fallback while content is loading
.title.hide-collapsed
= _('Time tracking')
- = icon('spinner spin', 'aria-hidden': 'true')
+ = loading_icon
- if issuable_sidebar.has_key?(:due_date)
.block.due_date
.sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable_sidebar[:due_date]) }
diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml
index de4df016cfb..7b5926fc186 100644
--- a/app/views/shared/issuable/_sidebar_todo.html.haml
+++ b/app/views/shared/issuable/_sidebar_todo.html.haml
@@ -12,4 +12,4 @@
data: todo_button_data }
%span.issuable-todo-inner.js-issuable-todo-inner<
= is_collapsed ? button_icon : button_title
- = icon('spin spinner', 'aria-hidden': 'true')
+ = loading_icon
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index dd743bd6ac4..86f20275c3b 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1452,6 +1452,14 @@
:weight: 1
:idempotent:
:tags: []
+- :name: issue_placement
+ :feature_category: :issue_tracking
+ :has_external_dependencies:
+ :urgency: :high
+ :resource_boundary: :cpu
+ :weight: 2
+ :idempotent: true
+ :tags: []
- :name: issue_rebalancing
:feature_category: :issue_tracking
:has_external_dependencies:
diff --git a/app/workers/issue_placement_worker.rb b/app/workers/issue_placement_worker.rb
new file mode 100644
index 00000000000..65bf3394153
--- /dev/null
+++ b/app/workers/issue_placement_worker.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class IssuePlacementWorker
+ include ApplicationWorker
+
+ idempotent!
+ feature_category :issue_tracking
+ urgency :high
+ worker_resource_boundary :cpu
+ weight 2
+
+ # Move at most the most recent 100 issues
+ QUERY_LIMIT = 100
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def perform(issue_id)
+ issue = Issue.id_in(issue_id).first
+ return unless issue
+
+ # Move the most recent 100 unpositioned items to the end.
+ # This is to deal with out-of-order execution of the worker,
+ # while preserving creation order.
+ to_place = Issue
+ .relative_positioning_query_base(issue)
+ .where(relative_position: nil)
+ .order({ created_at: :desc }, { id: :desc })
+ .limit(QUERY_LIMIT)
+
+ Issue.move_nulls_to_end(to_place.to_a.reverse)
+ Issues::BaseService.new(nil).rebalance_if_needed(to_place.max_by(&:relative_position))
+ rescue RelativePositioning::NoSpaceLeft => e
+ Gitlab::ErrorTracking.log_exception(e, issue_id: issue_id)
+ IssueRebalancingWorker.perform_async(nil, issue.project_id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+end
diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb
index e0e28767f8d..be9a168c3f6 100644
--- a/app/workers/new_issue_worker.rb
+++ b/app/workers/new_issue_worker.rb
@@ -12,8 +12,8 @@ class NewIssueWorker # rubocop:disable Scalability/IdempotentWorker
def perform(issue_id, user_id)
return unless objects_found?(issue_id, user_id)
- EventCreateService.new.open_issue(issuable, user)
- NotificationService.new.new_issue(issuable, user)
+ ::EventCreateService.new.open_issue(issuable, user)
+ ::NotificationService.new.new_issue(issuable, user)
issuable.create_cross_references!(user)
end
diff --git a/changelogs/unreleased/202267-migrate-spinner-for-app-assets-javascripts-vue_merge_request_widge.yml b/changelogs/unreleased/202267-migrate-spinner-for-app-assets-javascripts-vue_merge_request_widge.yml
new file mode 100644
index 00000000000..56976945c94
--- /dev/null
+++ b/changelogs/unreleased/202267-migrate-spinner-for-app-assets-javascripts-vue_merge_request_widge.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate '.fa-spinner' to '.spinner' for 'app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue'
+merge_request: 41142
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/202590-migrate-spinner-for-app-views-shared-issuable.yml b/changelogs/unreleased/202590-migrate-spinner-for-app-views-shared-issuable.yml
new file mode 100644
index 00000000000..d5ba511c735
--- /dev/null
+++ b/changelogs/unreleased/202590-migrate-spinner-for-app-views-shared-issuable.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate '.fa-spinner' to '.spinner' for 'app/views/shared/issuable'
+merge_request: 41132
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/205578-add-package-count-to-usage-data-28days.yml b/changelogs/unreleased/205578-add-package-count-to-usage-data-28days.yml
new file mode 100644
index 00000000000..9e162873f09
--- /dev/null
+++ b/changelogs/unreleased/205578-add-package-count-to-usage-data-28days.yml
@@ -0,0 +1,5 @@
+---
+title: Adds monthly package data to usage ping
+merge_request: 40452
+author:
+type: added
diff --git a/changelogs/unreleased/ajk-relative-positioning-async-move-to-end.yml b/changelogs/unreleased/ajk-relative-positioning-async-move-to-end.yml
new file mode 100644
index 00000000000..f10b774d659
--- /dev/null
+++ b/changelogs/unreleased/ajk-relative-positioning-async-move-to-end.yml
@@ -0,0 +1,5 @@
+---
+title: Ensure issue creation is not blocked by positioning
+merge_request: 41313
+author:
+type: fixed
diff --git a/changelogs/unreleased/dz-improve-new-issue-highlight.yml b/changelogs/unreleased/dz-improve-new-issue-highlight.yml
new file mode 100644
index 00000000000..5b2cc72971a
--- /dev/null
+++ b/changelogs/unreleased/dz-improve-new-issue-highlight.yml
@@ -0,0 +1,5 @@
+---
+title: Change logic behind new issues highlight
+merge_request: 41150
+author:
+type: changed
diff --git a/changelogs/unreleased/leipert-ajax-spinner-icons.yml b/changelogs/unreleased/leipert-ajax-spinner-icons.yml
new file mode 100644
index 00000000000..a934daa0c87
--- /dev/null
+++ b/changelogs/unreleased/leipert-ajax-spinner-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Change icon for branch delete button
+merge_request: 39968
+author:
+type: changed
diff --git a/changelogs/unreleased/rails-save-bang-21.yml b/changelogs/unreleased/rails-save-bang-21.yml
new file mode 100644
index 00000000000..4bf6d0c4520
--- /dev/null
+++ b/changelogs/unreleased/rails-save-bang-21.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Rails/SaveBang offenses for *spec/models/project_services*
+merge_request: 41320
+author: Rajendra Kadam
+type: fixed
diff --git a/config/feature_flags/development/security-on-demand-scans-site-validation.yml b/config/feature_flags/development/security_on_demand_scans_site_validation.yml
index 381c173b1bf..27ec926d9ac 100644
--- a/config/feature_flags/development/security-on-demand-scans-site-validation.yml
+++ b/config/feature_flags/development/security_on_demand_scans_site_validation.yml
@@ -1,5 +1,5 @@
---
-name: security-on-demand-scans-site-validation
+name: security_on_demand_scans_site_validation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40685
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241815
group: group::dynamic analysis
diff --git a/config/initializers/carrierwave_patch.rb b/config/initializers/carrierwave_patch.rb
index 53fba307926..ad3ff36138f 100644
--- a/config/initializers/carrierwave_patch.rb
+++ b/config/initializers/carrierwave_patch.rb
@@ -7,7 +7,9 @@ require "carrierwave/storage/fog"
#
# This patch also incorporates
# https://github.com/carrierwaveuploader/carrierwave/pull/2375 to
-# provide Azure support. This is already in CarrierWave v2.1.x, but
+# provide Azure support
+# and https://github.com/carrierwaveuploader/carrierwave/pull/2397 to
+# support custom expire_at. This is already in CarrierWave v2.1.x, but
# upgrading this gem is a significant task:
# https://gitlab.com/gitlab-org/gitlab/-/issues/216067
module CarrierWave
@@ -28,7 +30,7 @@ module CarrierWave
# avoid a get by using local references
local_directory = connection.directories.new(key: @uploader.fog_directory)
local_file = local_directory.files.new(key: path)
- expire_at = ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
+ expire_at = options[:expire_at] || ::Fog::Time.now + @uploader.fog_authenticated_url_expiration
case @uploader.fog_credentials[:provider]
when 'AWS', 'Google'
# Older versions of fog-google do not support options as a parameter
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 0452a9b6621..bb43e61775b 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -138,6 +138,8 @@
- 2
- - irker
- 1
+- - issue_placement
+ - 2
- - issue_rebalancing
- 1
- - jira_connect
diff --git a/db/migrate/20200901203055_add_id_created_at_index_to_packages.rb b/db/migrate/20200901203055_add_id_created_at_index_to_packages.rb
new file mode 100644
index 00000000000..d92309e3fef
--- /dev/null
+++ b/db/migrate/20200901203055_add_id_created_at_index_to_packages.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIdCreatedAtIndexToPackages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_packages_packages_on_id_and_created_at'
+
+ def up
+ add_concurrent_index :packages_packages, [:id, :created_at], name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name(:packages_packages, INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20200901203055 b/db/schema_migrations/20200901203055
new file mode 100644
index 00000000000..166f9069f40
--- /dev/null
+++ b/db/schema_migrations/20200901203055
@@ -0,0 +1 @@
+eb13fb285ac9af83bbc66397a5352a824575ad4af93178b98fbfc1be2e11ce8b \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 893cb8d9e31..754fbac8dd1 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20463,6 +20463,8 @@ CREATE INDEX index_packages_package_files_on_package_id_and_file_name ON public.
CREATE INDEX index_packages_packages_on_creator_id ON public.packages_packages USING btree (creator_id);
+CREATE INDEX index_packages_packages_on_id_and_created_at ON public.packages_packages USING btree (id, created_at);
+
CREATE INDEX index_packages_packages_on_name_trigram ON public.packages_packages USING gin (name public.gin_trgm_ops);
CREATE INDEX index_packages_packages_on_project_id_and_created_at ON public.packages_packages USING btree (project_id, created_at);
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 90d43aa3531..e1321deea21 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -6016,7 +6016,7 @@ type GeoNode {
name: String
"""
- Package file registries of the GeoNode. Available only when feature flag `geo_self_service_framework` is enabled
+ Package file registries of the GeoNode. Available only when feature flag `geo_package_file_replication` is enabled
"""
packageFileRegistries(
"""
@@ -6096,6 +6096,37 @@ type GeoNode {
syncObjectStorage: Boolean
"""
+ Find terraform state registries on this Geo node. Available only when feature
+ flag `geo_terraform_state_replication` is enabled
+ """
+ terraformStateRegistries(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Filters registries by their ID
+ """
+ ids: [ID!]
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+ ): TerraformStateRegistryConnection
+
+ """
The user-facing URL for this Geo node
"""
url: String
@@ -15713,6 +15744,86 @@ type TaskCompletionStatus {
}
"""
+Represents the sync and verification state of a terraform state
+"""
+type TerraformStateRegistry {
+ """
+ Timestamp when the TerraformStateRegistry was created
+ """
+ createdAt: Time
+
+ """
+ ID of the TerraformStateRegistry
+ """
+ id: ID!
+
+ """
+ Error message during sync of the TerraformStateRegistry
+ """
+ lastSyncFailure: String
+
+ """
+ Timestamp of the most recent successful sync of the TerraformStateRegistry
+ """
+ lastSyncedAt: Time
+
+ """
+ Timestamp after which the TerraformStateRegistry should be resynced
+ """
+ retryAt: Time
+
+ """
+ Number of consecutive failed sync attempts of the TerraformStateRegistry
+ """
+ retryCount: Int
+
+ """
+ Sync state of the TerraformStateRegistry
+ """
+ state: RegistryState
+
+ """
+ ID of the TerraformState
+ """
+ terraformStateId: ID!
+}
+
+"""
+The connection type for TerraformStateRegistry.
+"""
+type TerraformStateRegistryConnection {
+ """
+ A list of edges.
+ """
+ edges: [TerraformStateRegistryEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [TerraformStateRegistry]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type TerraformStateRegistryEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: TerraformStateRegistry
+}
+
+"""
Represents a requirement test report.
"""
type TestReport {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index a7f15dc6a35..36bc3f6e83d 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -16819,7 +16819,7 @@
},
{
"name": "packageFileRegistries",
- "description": "Package file registries of the GeoNode. Available only when feature flag `geo_self_service_framework` is enabled",
+ "description": "Package file registries of the GeoNode. Available only when feature flag `geo_package_file_replication` is enabled",
"args": [
{
"name": "ids",
@@ -17020,6 +17020,77 @@
"deprecationReason": null
},
{
+ "name": "terraformStateRegistries",
+ "description": "Find terraform state registries on this Geo node. Available only when feature flag `geo_terraform_state_replication` is enabled",
+ "args": [
+ {
+ "name": "ids",
+ "description": "Filters registries by their ID",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "TerraformStateRegistryConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "url",
"description": "The user-facing URL for this Geo node",
"args": [
@@ -46324,6 +46395,251 @@
},
{
"kind": "OBJECT",
+ "name": "TerraformStateRegistry",
+ "description": "Represents the sync and verification state of a terraform state",
+ "fields": [
+ {
+ "name": "createdAt",
+ "description": "Timestamp when the TerraformStateRegistry was created",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Time",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "id",
+ "description": "ID of the TerraformStateRegistry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lastSyncFailure",
+ "description": "Error message during sync of the TerraformStateRegistry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "lastSyncedAt",
+ "description": "Timestamp of the most recent successful sync of the TerraformStateRegistry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Time",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "retryAt",
+ "description": "Timestamp after which the TerraformStateRegistry should be resynced",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Time",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "retryCount",
+ "description": "Number of consecutive failed sync attempts of the TerraformStateRegistry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "state",
+ "description": "Sync state of the TerraformStateRegistry",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "ENUM",
+ "name": "RegistryState",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "terraformStateId",
+ "description": "ID of the TerraformState",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ID",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "TerraformStateRegistryConnection",
+ "description": "The connection type for TerraformStateRegistry.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "TerraformStateRegistryEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "TerraformStateRegistry",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "TerraformStateRegistryEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "TerraformStateRegistry",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "TestReport",
"description": "Represents a requirement test report.",
"fields": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 136286f355b..829f18b1b18 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -2317,6 +2317,21 @@ Completion status of tasks
| `completedCount` | Int! | Number of completed tasks |
| `count` | Int! | Number of total tasks |
+## TerraformStateRegistry
+
+Represents the sync and verification state of a terraform state
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `createdAt` | Time | Timestamp when the TerraformStateRegistry was created |
+| `id` | ID! | ID of the TerraformStateRegistry |
+| `lastSyncFailure` | String | Error message during sync of the TerraformStateRegistry |
+| `lastSyncedAt` | Time | Timestamp of the most recent successful sync of the TerraformStateRegistry |
+| `retryAt` | Time | Timestamp after which the TerraformStateRegistry should be resynced |
+| `retryCount` | Int | Number of consecutive failed sync attempts of the TerraformStateRegistry |
+| `state` | RegistryState | Sync state of the TerraformStateRegistry |
+| `terraformStateId` | ID! | ID of the TerraformState |
+
## TestReport
Represents a requirement test report.
diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md
index 75f938cbc59..c2aa0ff233d 100644
--- a/doc/development/geo/framework.md
+++ b/doc/development/geo/framework.md
@@ -566,7 +566,7 @@ the Admin Area UI, and Prometheus!
null: true,
resolver: ::Resolvers::Geo::WidgetRegistriesResolver,
description: 'Find widget registries on this Geo node',
- feature_flag: :geo_self_service_framework
+ feature_flag: :geo_widget_replication
```
1. Add the new `widget_registries` field name to the `expected_fields` array in
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 722b49a3af9..1e70770856e 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -138,7 +138,90 @@ const label = __('Subscribe');
```
In order to test JavaScript translations you have to change the GitLab
-localization to other language than English and you have to generate JSON files
+localization to another language than English and you have to generate JSON files
+using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
+
+### Vue files
+
+In Vue files we make both the `__()` (double underscore parenthesis) function and the `s__()` (namespaced double underscore parenthesis) function available that you can import from the `~/locale` file. For instance:
+
+```javascript
+import { __, s__ } from '~/locale';
+const label = __('Subscribe');
+const nameSpacedlabel = __('Plan|Subscribe');
+```
+
+For the static text strings we suggest two patterns for using these translations in Vue files:
+
+- External constants file:
+
+ ```javascript
+ javascripts
+ │
+ └───alert_settings
+ │ │ constants.js
+ │ └───components
+ │ │ alert_settings_form.vue
+
+
+ // constants.js
+
+ import { s__ } from '~/locale';
+
+ /* Integration constants */
+
+ export const I18N_ALERT_SETTINGS_FORM = {
+ saveBtnLabel: __('Save changes'),
+ };
+
+
+ // alert_settings_form.vue
+
+ import {
+ I18N_ALERT_SETTINGS_FORM,
+ } from '../constants';
+
+ <script>
+ export default {
+ i18n: {
+ I18N_ALERT_SETTINGS_FORM,
+ }
+ }
+ </script>
+
+ <template>
+ <gl-button
+ ref="submitBtn"
+ variant="success"
+ type="submit"
+ >
+ {{ $options.i18n.I18N_ALERT_SETTINGS_FORM }}
+ </gl-button>
+ </template>
+ ```
+
+ When possible, you should opt for this pattern, as this allows you to import these strings directly into your component specs for re-use during testing.
+
+- Internal component `$options` object `:
+
+ ```javascript
+ <script>
+ export default {
+ i18n: {
+ buttonLabel: s__('Plan|Button Label')
+ }
+ },
+ </script>
+
+ <template>
+ <gl-button :aria-label="$options.i18n.buttonLabel">
+ {{ $options.i18n.buttonLabel }}
+ </gl-button>
+ </template>
+ ```
+
+In order to visually test the Vue translations you have to change the GitLab
+localization to another language than English and you have to generate JSON files
using `bin/rake gettext:po_to_json` or `bin/rake gettext:compile`.
### Dynamic translations
diff --git a/doc/install/installation.md b/doc/install/installation.md
index f0a5970ef87..3e5668be18a 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -147,8 +147,7 @@ ldd $(command -v git) | grep pcre2
The output should contain `libpcre2-8.so.0`.
-Is the system packaged Git too old, or not compiled with pcre2?
-Remove it:
+If the system packaged Git is too old or not compiled with `pcre2`, remove it:
```shell
sudo apt-get remove git-core
diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md
index 0f29b2be54e..34c75b6b04c 100644
--- a/doc/user/packages/container_registry/index.md
+++ b/doc/user/packages/container_registry/index.md
@@ -70,7 +70,7 @@ This view allows you to:
- Filter image repositories by their name.
- [Delete](#delete-images-from-within-gitlab) one or more image repository.
- Navigate to the image repository details page.
-- Show a **Quick start** dropdown with the most common commands to log in, build and push
+- Show a **Quick start** dropdown with the most common commands to log in, build and push.
- Show a banner if the optional [cleanup policy](#cleanup-policy) is enabled for this project.
### Control Container Registry for your group
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 22c344a27f7..5ea84a35d17 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -181,6 +181,7 @@ module Gitlab
successful_deployments: deployment_count(Deployment.success.where(last_28_days_time_period)),
failed_deployments: deployment_count(Deployment.failed.where(last_28_days_time_period)),
# rubocop: enable UsageData/LargeTable:
+ packages: count(::Packages::Package.where(last_28_days_time_period)),
personal_snippets: count(PersonalSnippet.where(last_28_days_time_period)),
project_snippets: count(ProjectSnippet.where(last_28_days_time_period))
}.tap do |data|
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index acd052ab99d..dc3e6580b26 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7779,6 +7779,9 @@ msgstr ""
msgid "DastProfiles|Do you want to discard your changes?"
msgstr ""
+msgid "DastProfiles|Download validation text file"
+msgstr ""
+
msgid "DastProfiles|Edit feature will come soon. Please create a new profile if changes needed"
msgstr ""
@@ -7839,21 +7842,57 @@ msgstr ""
msgid "DastProfiles|Site Profiles"
msgstr ""
+msgid "DastProfiles|Site is not validated yet, please follow the steps."
+msgstr ""
+
+msgid "DastProfiles|Site must be validated to run an active scan."
+msgstr ""
+
msgid "DastProfiles|Spider timeout"
msgstr ""
+msgid "DastProfiles|Step 1 - Choose site validation method"
+msgstr ""
+
+msgid "DastProfiles|Step 2 - Add following text to the target site"
+msgstr ""
+
+msgid "DastProfiles|Step 3 - Confirm text file location and validate"
+msgstr ""
+
msgid "DastProfiles|Target URL"
msgstr ""
msgid "DastProfiles|Target timeout"
msgstr ""
+msgid "DastProfiles|Text file validation"
+msgstr ""
+
msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the spider to traverse the site."
msgstr ""
+msgid "DastProfiles|Validate"
+msgstr ""
+
+msgid "DastProfiles|Validate target site"
+msgstr ""
+
+msgid "DastProfiles|Validating..."
+msgstr ""
+
+msgid "DastProfiles|Validation failed, please make sure that you follow the steps above with the choosen method."
+msgstr ""
+
+msgid "DastProfiles|Validation must be turned off to change the target URL"
+msgstr ""
+
+msgid "DastProfiles|Validation succeeded. Both active and passive scans can be run against the target site."
+msgstr ""
+
msgid "Data is still calculating..."
msgstr ""
diff --git a/package.json b/package.json
index 2b25df68d86..ce902a96581 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
"@gitlab/ui": "20.18.1",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
- "@sentry/browser": "^5.10.2",
+ "@sentry/browser": "^5.22.3",
"@sourcegraph/code-host-integration": "0.0.50",
"@toast-ui/editor": "^2.3.1",
"@toast-ui/vue-editor": "^2.3.1",
diff --git a/spec/controllers/concerns/send_file_upload_spec.rb b/spec/controllers/concerns/send_file_upload_spec.rb
index 9adec42842c..747ccd7ba1b 100644
--- a/spec/controllers/concerns/send_file_upload_spec.rb
+++ b/spec/controllers/concerns/send_file_upload_spec.rb
@@ -79,6 +79,23 @@ RSpec.describe SendFileUpload do
it_behaves_like 'handles image resize requests allowed by FFs'
end
+ context 'when boths FFs are enabled globally' do
+ before do
+ stub_feature_flags(dynamic_image_resizing_requester: true)
+ stub_feature_flags(dynamic_image_resizing_owner: true)
+ end
+
+ it_behaves_like 'handles image resize requests allowed by FFs'
+
+ context 'when current_user is nil' do
+ before do
+ allow(controller).to receive(:current_user).and_return(nil)
+ end
+
+ it_behaves_like 'handles image resize requests allowed by FFs'
+ end
+ end
+
context 'when only FF based on content requester is enabled for current user' do
before do
stub_feature_flags(dynamic_image_resizing_requester: image_requester)
diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb
index f6514b9b973..8b0df81422b 100644
--- a/spec/factories/usage_data.rb
+++ b/spec/factories/usage_data.rb
@@ -101,6 +101,7 @@ FactoryBot.define do
create(:package, project: projects[0])
create(:package, project: projects[0])
create(:package, project: projects[1])
+ create(:package, created_at: 2.months.ago, project: projects[1])
ProjectFeature.first.update_attribute('repository_access_level', 0)
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index 77de9ae4f9f..84c39ad65e9 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -12,7 +12,7 @@
"title": { "type": "string" },
"confidential": { "type": "boolean" },
"due_date": { "type": ["date", "null"] },
- "relative_position": { "type": "integer" },
+ "relative_position": { "type": ["integer", "null"] },
"time_estimate": { "type": "integer" },
"issue_sidebar_endpoint": { "type": "string" },
"toggle_subscription_endpoint": { "type": "string" },
diff --git a/spec/frontend/ajax_loading_spinner_spec.js b/spec/frontend/ajax_loading_spinner_spec.js
deleted file mode 100644
index c5d66eb2143..00000000000
--- a/spec/frontend/ajax_loading_spinner_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import $ from 'jquery';
-import AjaxLoadingSpinner from '~/ajax_loading_spinner';
-
-describe('Ajax Loading Spinner', () => {
- const fixtureTemplate = 'static/ajax_loading_spinner.html';
- preloadFixtures(fixtureTemplate);
-
- beforeEach(() => {
- loadFixtures(fixtureTemplate);
- AjaxLoadingSpinner.init();
- });
-
- it('change current icon with spinner icon and disable link while waiting ajax response', done => {
- jest.spyOn($, 'ajax').mockImplementation(req => {
- const xhr = new XMLHttpRequest();
- const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
- const icon = ajaxLoadingSpinner.querySelector('i');
-
- req.beforeSend(xhr, { dataType: 'text/html' });
-
- expect(icon).not.toHaveClass('fa-trash-o');
- expect(icon).toHaveClass('gl-spinner');
- expect(icon).toHaveClass('gl-spinner-orange');
- expect(icon).toHaveClass('gl-spinner-sm');
- expect(icon.dataset.icon).toEqual('fa-trash-o');
- expect(ajaxLoadingSpinner.getAttribute('disabled')).toEqual('');
-
- req.complete({});
-
- done();
- const deferred = $.Deferred();
- return deferred.promise();
- });
- document.querySelector('.js-ajax-loading-spinner').click();
- });
-
- it('use original icon again and enabled the link after complete the ajax request', done => {
- jest.spyOn($, 'ajax').mockImplementation(req => {
- const xhr = new XMLHttpRequest();
- const ajaxLoadingSpinner = document.querySelector('.js-ajax-loading-spinner');
-
- req.beforeSend(xhr, { dataType: 'text/html' });
- req.complete({});
-
- const icon = ajaxLoadingSpinner.querySelector('i');
-
- expect(icon).toHaveClass('fa-trash-o');
- expect(icon).not.toHaveClass('gl-spinner');
- expect(icon).not.toHaveClass('gl-spinner-orange');
- expect(icon).not.toHaveClass('gl-spinner-sm');
- expect(ajaxLoadingSpinner.getAttribute('disabled')).toEqual(null);
-
- done();
- const deferred = $.Deferred();
- return deferred.promise();
- });
- document.querySelector('.js-ajax-loading-spinner').click();
- });
-});
diff --git a/spec/frontend/branches/ajax_loading_spinner_spec.js b/spec/frontend/branches/ajax_loading_spinner_spec.js
new file mode 100644
index 00000000000..a6404faa445
--- /dev/null
+++ b/spec/frontend/branches/ajax_loading_spinner_spec.js
@@ -0,0 +1,32 @@
+import AjaxLoadingSpinner from '~/branches/ajax_loading_spinner';
+
+describe('Ajax Loading Spinner', () => {
+ let ajaxLoadingSpinnerElement;
+ let fauxEvent;
+ beforeEach(() => {
+ document.body.innerHTML = `
+ <div>
+ <a class="js-ajax-loading-spinner"
+ data-remote
+ href="http://goesnowhere.nothing/whereami">
+ <i class="fa fa-trash-o"></i>
+ </a></div>`;
+ AjaxLoadingSpinner.init();
+ ajaxLoadingSpinnerElement = document.querySelector('.js-ajax-loading-spinner');
+ fauxEvent = { target: ajaxLoadingSpinnerElement };
+ });
+
+ afterEach(() => {
+ document.body.innerHTML = '';
+ });
+
+ it('`ajaxBeforeSend` event handler sets current icon to spinner and disables link', () => {
+ expect(ajaxLoadingSpinnerElement.parentNode.querySelector('.gl-spinner')).toBeNull();
+ expect(ajaxLoadingSpinnerElement.classList.contains('hidden')).toBe(false);
+
+ AjaxLoadingSpinner.ajaxBeforeSend(fauxEvent);
+
+ expect(ajaxLoadingSpinnerElement.parentNode.querySelector('.gl-spinner')).not.toBeNull();
+ expect(ajaxLoadingSpinnerElement.classList.contains('hidden')).toBe(true);
+ });
+});
diff --git a/spec/frontend/fixtures/static/ajax_loading_spinner.html b/spec/frontend/fixtures/static/ajax_loading_spinner.html
deleted file mode 100644
index 0e1ebb32b1c..00000000000
--- a/spec/frontend/fixtures/static/ajax_loading_spinner.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<a class="js-ajax-loading-spinner" data-remote href="http://goesnowhere.nothing/whereami">
-<i class="fa fa-trash-o"></i>
-</a>
diff --git a/spec/frontend/packages/details/components/additional_metadata_spec.js b/spec/frontend/packages/details/components/additional_metadata_spec.js
index b2337b86740..111e4205abb 100644
--- a/spec/frontend/packages/details/components/additional_metadata_spec.js
+++ b/spec/frontend/packages/details/components/additional_metadata_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
-import DetailsRow from '~/registry/shared/components/details_row.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
import component from '~/packages/details/components/additional_metadata.vue';
import { mavenPackage, conanPackage, nugetPackage, npmPackage } from '../../mock_data';
diff --git a/spec/frontend/packages/details/components/package_history_spec.js b/spec/frontend/packages/details/components/package_history_spec.js
index 28bcbbb2b1b..f745a457b0a 100644
--- a/spec/frontend/packages/details/components/package_history_spec.js
+++ b/spec/frontend/packages/details/components/package_history_spec.js
@@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { GlLink, GlSprintf } from '@gitlab/ui';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-import HistoryElement from '~/packages/details/components/history_element.vue';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
import component from '~/packages/details/components/package_history.vue';
import { mavenPackage, mockPipelineInfo } from '../../mock_data';
@@ -17,8 +17,8 @@ describe('Package History', () => {
wrapper = shallowMount(component, {
propsData: { ...defaultProps, ...props },
stubs: {
- HistoryElement: {
- props: HistoryElement.props,
+ HistoryItem: {
+ props: HistoryItem.props,
template: '<div data-testid="history-element"><slot></slot></div>',
},
GlSprintf,
diff --git a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
index a21facefc97..ef22979ca7d 100644
--- a/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
+++ b/spec/frontend/registry/explorer/components/details_page/tags_list_row_spec.js
@@ -6,7 +6,7 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import component from '~/registry/explorer/components/details_page/tags_list_row.vue';
import DeleteButton from '~/registry/explorer/components/delete_button.vue';
-import DetailsRow from '~/registry/shared/components/details_row.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
import {
REMOVE_TAG_BUTTON_TITLE,
REMOVE_TAG_BUTTON_DISABLE_TOOLTIP,
diff --git a/spec/frontend/packages/details/components/__snapshots__/history_element_spec.js.snap b/spec/frontend/vue_shared/components/registry/__snapshots__/history_item_spec.js.snap
index a1751d69c70..2abae33bc19 100644
--- a/spec/frontend/packages/details/components/__snapshots__/history_element_spec.js.snap
+++ b/spec/frontend/vue_shared/components/registry/__snapshots__/history_item_spec.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`History Element renders the correct markup 1`] = `
+exports[`History Item renders the correct markup 1`] = `
<li
class="timeline-entry system-note note-wrapper gl-mb-6!"
>
@@ -31,7 +31,11 @@ exports[`History Element renders the correct markup 1`] = `
<div
class="note-body"
- />
+ >
+ <div
+ data-testid="body-slot"
+ />
+ </div>
</div>
</div>
</li>
diff --git a/spec/frontend/registry/shared/components/details_row_spec.js b/spec/frontend/vue_shared/components/registry/details_row_spec.js
index 5ae4e0ab37f..16a55b84787 100644
--- a/spec/frontend/registry/shared/components/details_row_spec.js
+++ b/spec/frontend/vue_shared/components/registry/details_row_spec.js
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
-import component from '~/registry/shared/components/details_row.vue';
+import component from '~/vue_shared/components/registry/details_row.vue';
describe('DetailsRow', () => {
let wrapper;
diff --git a/spec/frontend/packages/details/components/history_element_spec.js b/spec/frontend/vue_shared/components/registry/history_item_spec.js
index e8746fc93f5..d51ddda2e3e 100644
--- a/spec/frontend/packages/details/components/history_element_spec.js
+++ b/spec/frontend/vue_shared/components/registry/history_item_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import { GlIcon } from '@gitlab/ui';
-import component from '~/packages/details/components/history_element.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
+import component from '~/vue_shared/components/registry/history_item.vue';
-describe('History Element', () => {
+describe('History Item', () => {
let wrapper;
const defaultProps = {
icon: 'pencil',
@@ -17,6 +17,7 @@ describe('History Element', () => {
},
slots: {
default: '<div data-testid="default-slot"></div>',
+ body: '<div data-testid="body-slot"></div>',
},
});
};
@@ -29,6 +30,7 @@ describe('History Element', () => {
const findTimelineEntry = () => wrapper.find(TimelineEntryItem);
const findGlIcon = () => wrapper.find(GlIcon);
const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]');
+ const findBodySlot = () => wrapper.find('[data-testid="body-slot"]');
it('renders the correct markup', () => {
mountComponent();
@@ -41,11 +43,19 @@ describe('History Element', () => {
expect(findDefaultSlot().exists()).toBe(true);
});
+
+ it('has a body slot', () => {
+ mountComponent();
+
+ expect(findBodySlot().exists()).toBe(true);
+ });
+
it('has a timeline entry', () => {
mountComponent();
expect(findTimelineEntry().exists()).toBe(true);
});
+
it('has an icon', () => {
mountComponent();
diff --git a/spec/initializers/carrierwave_patch_spec.rb b/spec/initializers/carrierwave_patch_spec.rb
index d577eca2ac7..c4a7bfa59c6 100644
--- a/spec/initializers/carrierwave_patch_spec.rb
+++ b/spec/initializers/carrierwave_patch_spec.rb
@@ -28,5 +28,17 @@ RSpec.describe 'CarrierWave::Storage::Fog::File' do
expect(subject.authenticated_url).to eq("https://sa.blob.core.windows.net/test_container/test_blob?token")
end
end
+
+ context 'with custom expire_at' do
+ it 'properly sets expires param' do
+ expire_at = 24.hours.from_now
+
+ expect_next_instance_of(Fog::Storage::AzureRM::File) do |file|
+ expect(file).to receive(:url).with(expire_at).and_call_original
+ end
+
+ expect(subject.authenticated_url(expire_at: expire_at)).to eq("https://sa.blob.core.windows.net/test_container/test_blob?token")
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index a119c85401d..a036cb95f63 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -479,7 +479,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(count_data[:project_snippets]).to eq(4)
expect(count_data[:projects_with_packages]).to eq(2)
- expect(count_data[:packages]).to eq(3)
+ expect(count_data[:packages]).to eq(4)
end
it 'gathers object store usage correctly' do
@@ -572,6 +572,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(counts_monthly[:snippets]).to eq(3)
expect(counts_monthly[:personal_snippets]).to eq(1)
expect(counts_monthly[:project_snippets]).to eq(2)
+ expect(counts_monthly[:packages]).to eq(3)
end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 46fe942fec1..8d2eb3b5e2a 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -295,20 +295,14 @@ RSpec.describe Issuable do
end
describe "#new?" do
- it "returns true when created today and record hasn't been updated" do
- allow(issue).to receive(:today?).and_return(true)
- expect(issue.new?).to be_truthy
- end
-
- it "returns false when not created today" do
- allow(issue).to receive(:today?).and_return(false)
+ it "returns false when created 30 hours ago" do
+ allow(issue).to receive(:created_at).and_return(Time.current - 30.hours)
expect(issue.new?).to be_falsey
end
- it "returns false when record has been updated" do
- allow(issue).to receive(:today?).and_return(true)
- issue.update_attribute(:updated_at, 1.hour.ago)
- expect(issue.new?).to be_falsey
+ it "returns true when created 20 hours ago" do
+ allow(issue).to receive(:created_at).and_return(Time.current - 20.hours)
+ expect(issue.new?).to be_truthy
end
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 4d2474cc56a..45afbcca96d 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe BambooService, :use_clean_rails_memory_store_caching do
let_it_be(:project) { create(:project) }
subject(:service) do
- described_class.create(
+ described_class.create!(
project: project,
properties: {
bamboo_url: bamboo_url,
@@ -85,7 +85,7 @@ RSpec.describe BambooService, :use_clean_rails_memory_store_caching do
bamboo_service = service
bamboo_service.bamboo_url = 'http://gitlab1.com'
- bamboo_service.save
+ bamboo_service.save!
expect(bamboo_service.password).to be_nil
end
@@ -94,7 +94,7 @@ RSpec.describe BambooService, :use_clean_rails_memory_store_caching do
bamboo_service = service
bamboo_service.username = 'some_name'
- bamboo_service.save
+ bamboo_service.save!
expect(bamboo_service.password).to eq('password')
end
@@ -104,7 +104,7 @@ RSpec.describe BambooService, :use_clean_rails_memory_store_caching do
bamboo_service.bamboo_url = 'http://gitlab_edited.com'
bamboo_service.password = 'password'
- bamboo_service.save
+ bamboo_service.save!
expect(bamboo_service.password).to eq('password')
expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com')
@@ -117,7 +117,7 @@ RSpec.describe BambooService, :use_clean_rails_memory_store_caching do
bamboo_service.bamboo_url = 'http://gitlab_edited.com'
bamboo_service.password = 'password'
- bamboo_service.save
+ bamboo_service.save!
expect(bamboo_service.password).to eq('password')
expect(bamboo_service.bamboo_url).to eq('http://gitlab_edited.com')
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 3d0c2cc1006..f6bf1551bf0 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe BuildkiteService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
subject(:service) do
- described_class.create(
+ described_class.create!(
project: project,
properties: {
service_hook: true,
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index fdfee9894fa..6da16905363 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe JiraService do
}
end
- let(:service) { described_class.create(options) }
+ let(:service) { described_class.create!(options) }
it 'sets the URL properly' do
# jira-ruby gem parses the URI and handles trailing slashes fine:
@@ -102,7 +102,7 @@ RSpec.describe JiraService do
}
end
- subject { described_class.create(params) }
+ subject { described_class.create!(params) }
it 'does not store data into properties' do
expect(subject.properties).to be_nil
@@ -189,7 +189,7 @@ RSpec.describe JiraService do
let_it_be(:new_url) { 'http://jira-new.example.com' }
before do
- service.update(username: new_username, url: new_url)
+ service.update!(username: new_username, url: new_url)
end
it 'leaves properties field emtpy' do
@@ -209,12 +209,12 @@ RSpec.describe JiraService do
context 'when updating the url, api_url, username, or password' do
it 'updates deployment type' do
- service.update(url: 'http://first.url')
- service.jira_tracker_data.update(deployment_type: 'server')
+ service.update!(url: 'http://first.url')
+ service.jira_tracker_data.update!(deployment_type: 'server')
expect(service.jira_tracker_data.deployment_server?).to be_truthy
- service.update(api_url: 'http://another.url')
+ service.update!(api_url: 'http://another.url')
service.jira_tracker_data.reload
expect(service.jira_tracker_data.deployment_cloud?).to be_truthy
@@ -222,25 +222,25 @@ RSpec.describe JiraService do
end
it 'calls serverInfo for url' do
- service.update(url: 'http://first.url')
+ service.update!(url: 'http://first.url')
expect(WebMock).to have_requested(:get, /serverInfo/)
end
it 'calls serverInfo for api_url' do
- service.update(api_url: 'http://another.url')
+ service.update!(api_url: 'http://another.url')
expect(WebMock).to have_requested(:get, /serverInfo/)
end
it 'calls serverInfo for username' do
- service.update(username: 'test-user')
+ service.update!(username: 'test-user')
expect(WebMock).to have_requested(:get, /serverInfo/)
end
it 'calls serverInfo for password' do
- service.update(password: 'test-password')
+ service.update!(password: 'test-password')
expect(WebMock).to have_requested(:get, /serverInfo/)
end
@@ -248,7 +248,7 @@ RSpec.describe JiraService do
context 'when not updating the url, api_url, username, or password' do
it 'does not update deployment type' do
- service.update(jira_issue_transition_id: 'jira_issue_transition_id')
+ expect {service.update!(jira_issue_transition_id: 'jira_issue_transition_id')}.to raise_error(ActiveRecord::RecordInvalid)
expect(WebMock).not_to have_requested(:get, /serverInfo/)
end
@@ -268,7 +268,7 @@ RSpec.describe JiraService do
it 'resets password if url changed' do
service
service.url = 'http://jira_edited.example.com'
- service.save
+ service.save!
expect(service.reload.url).to eq('http://jira_edited.example.com')
expect(service.password).to be_nil
@@ -276,7 +276,7 @@ RSpec.describe JiraService do
it 'does not reset password if url "changed" to the same url as before' do
service.url = 'http://jira.example.com'
- service.save
+ service.save!
expect(service.reload.url).to eq('http://jira.example.com')
expect(service.password).not_to be_nil
@@ -284,7 +284,7 @@ RSpec.describe JiraService do
it 'resets password if url not changed but api url added' do
service.api_url = 'http://jira_edited.example.com/rest/api/2'
- service.save
+ service.save!
expect(service.reload.api_url).to eq('http://jira_edited.example.com/rest/api/2')
expect(service.password).to be_nil
@@ -293,7 +293,7 @@ RSpec.describe JiraService do
it 'does not reset password if new url is set together with password, even if it\'s the same password' do
service.url = 'http://jira_edited.example.com'
service.password = password
- service.save
+ service.save!
expect(service.password).to eq(password)
expect(service.url).to eq('http://jira_edited.example.com')
@@ -302,14 +302,14 @@ RSpec.describe JiraService do
it 'resets password if url changed, even if setter called multiple times' do
service.url = 'http://jira1.example.com/rest/api/2'
service.url = 'http://jira1.example.com/rest/api/2'
- service.save
+ service.save!
expect(service.password).to be_nil
end
it 'does not reset password if username changed' do
service.username = 'some_name'
- service.save
+ service.save!
expect(service.reload.password).to eq(password)
end
@@ -317,7 +317,7 @@ RSpec.describe JiraService do
it 'does not reset password if password changed' do
service.url = 'http://jira_edited.example.com'
service.password = 'new_password'
- service.save
+ service.save!
expect(service.reload.password).to eq('new_password')
end
@@ -325,7 +325,7 @@ RSpec.describe JiraService do
it 'does not reset password if the password is touched and same as before' do
service.url = 'http://jira_edited.example.com'
service.password = password
- service.save
+ service.save!
expect(service.reload.password).to eq(password)
end
@@ -342,20 +342,20 @@ RSpec.describe JiraService do
it 'resets password if api url changed' do
service.api_url = 'http://jira_edited.example.com/rest/api/2'
- service.save
+ service.save!
expect(service.password).to be_nil
end
it 'does not reset password if url changed' do
service.url = 'http://jira_edited.example.com'
- service.save
+ service.save!
expect(service.password).to eq(password)
end
it 'resets password if api url set to empty' do
- service.update(api_url: '')
+ service.update!(api_url: '')
expect(service.reload.password).to be_nil
end
@@ -372,7 +372,7 @@ RSpec.describe JiraService do
it 'saves password if new url is set together with password' do
service.url = 'http://jira_edited.example.com/rest/api/2'
service.password = 'password'
- service.save
+ service.save!
expect(service.reload.password).to eq('password')
expect(service.reload.url).to eq('http://jira_edited.example.com/rest/api/2')
end
@@ -441,7 +441,7 @@ RSpec.describe JiraService do
allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return('JIRA-123')
allow(JIRA::Resource::Remotelink).to receive(:all).and_return([])
- @jira_service.save
+ @jira_service.save!
project_issues_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123'
@transitions_url = 'http://jira.example.com/rest/api/2/issue/JIRA-123/transitions'
@@ -709,9 +709,11 @@ RSpec.describe JiraService do
describe '#test' do
let(:server_info_results) { { 'url' => 'http://url', 'deploymentType' => 'Cloud' } }
+ let_it_be(:project) { create(:project, :repository) }
let(:jira_service) do
described_class.new(
url: url,
+ project: project,
username: username,
password: password
)
@@ -728,7 +730,7 @@ RSpec.describe JiraService do
end
it 'gets Jira project with API URL if set' do
- jira_service.update(api_url: 'http://jira.api.com')
+ jira_service.update!(api_url: 'http://jira.api.com')
expect(server_info).to eq(success: true, result: server_info_results)
expect(WebMock).to have_requested(:get, /jira.api.com/)
diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb
index 35f282b638b..33b5c9809c7 100644
--- a/spec/models/project_services/packagist_service_spec.rb
+++ b/spec/models/project_services/packagist_service_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe PackagistService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) }
- let(:packagist_service) { described_class.create(packagist_params) }
+ let(:packagist_service) { described_class.create!(packagist_params) }
before do
stub_request(:post, packagist_hook_url)
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index 9a8386c619e..21cc5d44558 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -81,7 +81,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'when pipeline is succeeded' do
before do
data[:object_attributes][:status] = 'success'
- pipeline.update(status: 'success')
+ pipeline.update!(status: 'success')
end
it_behaves_like 'sending email'
@@ -91,7 +91,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'on default branch' do
before do
data[:object_attributes][:ref] = project.default_branch
- pipeline.update(ref: project.default_branch)
+ pipeline.update!(ref: project.default_branch)
end
context 'notifications are enabled only for default branch' do
@@ -115,7 +115,7 @@ RSpec.describe PipelinesEmailService, :mailer do
before do
create(:protected_branch, project: project, name: 'a-protected-branch')
data[:object_attributes][:ref] = 'a-protected-branch'
- pipeline.update(ref: 'a-protected-branch')
+ pipeline.update!(ref: 'a-protected-branch')
end
context 'notifications are enabled only for default branch' do
@@ -138,7 +138,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'on a neither protected nor default branch' do
before do
data[:object_attributes][:ref] = 'a-random-branch'
- pipeline.update(ref: 'a-random-branch')
+ pipeline.update!(ref: 'a-random-branch')
end
context 'notifications are enabled only for default branch' do
@@ -177,7 +177,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'with succeeded pipeline' do
before do
data[:object_attributes][:status] = 'success'
- pipeline.update(status: 'success')
+ pipeline.update!(status: 'success')
end
it_behaves_like 'not sending email'
@@ -195,7 +195,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'with succeeded pipeline' do
before do
data[:object_attributes][:status] = 'success'
- pipeline.update(status: 'success')
+ pipeline.update!(status: 'success')
end
it_behaves_like 'not sending email'
@@ -206,7 +206,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'on default branch' do
before do
data[:object_attributes][:ref] = project.default_branch
- pipeline.update(ref: project.default_branch)
+ pipeline.update!(ref: project.default_branch)
end
context 'notifications are enabled only for default branch' do
@@ -230,7 +230,7 @@ RSpec.describe PipelinesEmailService, :mailer do
before do
create(:protected_branch, project: project, name: 'a-protected-branch')
data[:object_attributes][:ref] = 'a-protected-branch'
- pipeline.update(ref: 'a-protected-branch')
+ pipeline.update!(ref: 'a-protected-branch')
end
context 'notifications are enabled only for default branch' do
@@ -253,7 +253,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'on a neither protected nor default branch' do
before do
data[:object_attributes][:ref] = 'a-random-branch'
- pipeline.update(ref: 'a-random-branch')
+ pipeline.update!(ref: 'a-random-branch')
end
context 'notifications are enabled only for default branch' do
@@ -281,7 +281,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'with failed pipeline' do
before do
data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
+ pipeline.update!(status: 'failed')
end
it_behaves_like 'not sending email'
@@ -295,7 +295,7 @@ RSpec.describe PipelinesEmailService, :mailer do
context 'with failed pipeline' do
before do
data[:object_attributes][:status] = 'failed'
- pipeline.update(status: 'failed')
+ pipeline.update!(status: 'failed')
end
it_behaves_like 'sending email'
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index a3fda33664a..f71dad86a08 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -11,7 +11,7 @@ RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do
let(:project) { create(:project) }
subject(:service) do
- described_class.create(
+ described_class.create!(
project: project,
properties: {
teamcity_url: teamcity_url,
@@ -85,7 +85,7 @@ RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do
teamcity_service = service
teamcity_service.teamcity_url = 'http://gitlab1.com'
- teamcity_service.save
+ teamcity_service.save!
expect(teamcity_service.password).to be_nil
end
@@ -94,7 +94,7 @@ RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do
teamcity_service = service
teamcity_service.username = 'some_name'
- teamcity_service.save
+ teamcity_service.save!
expect(teamcity_service.password).to eq('password')
end
@@ -104,7 +104,7 @@ RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do
teamcity_service.teamcity_url = 'http://gitlab_edited.com'
teamcity_service.password = 'password'
- teamcity_service.save
+ teamcity_service.save!
expect(teamcity_service.password).to eq('password')
expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com')
@@ -117,7 +117,7 @@ RSpec.describe TeamcityService, :use_clean_rails_memory_store_caching do
teamcity_service.teamcity_url = 'http://gitlab_edited.com'
teamcity_service.password = 'password'
- teamcity_service.save
+ teamcity_service.save!
expect(teamcity_service.password).to eq('password')
expect(teamcity_service.teamcity_url).to eq('http://gitlab_edited.com')
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 794da96ed11..2c36b7ea4b6 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -75,35 +75,10 @@ RSpec.describe Issues::CreateService do
expect(Todo.where(attributes).count).to eq 1
end
- it 'rebalances if needed' do
- create(:issue, project: project, relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
+ it 'moves the issue to the end, in an asynchronous worker' do
+ expect(IssuePlacementWorker).to receive(:perform_async).with(Integer)
- expect(issue.relative_position).to eq(project.issues.maximum(:relative_position))
- end
-
- it 'does not rebalance if the flag is disabled' do
- stub_feature_flags(rebalance_issues: false)
-
- create(:issue, project: project, relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).not_to receive(:perform_async)
-
- expect(issue.relative_position).to eq(project.issues.maximum(:relative_position))
- end
-
- it 'does rebalance if the flag is enabled for the project' do
- stub_feature_flags(rebalance_issues: project)
-
- create(:issue, project: project, relative_position: RelativePositioning::MAX_POSITION)
- expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
-
- expect(issue.relative_position).to eq(project.issues.maximum(:relative_position))
- end
-
- it 'does not rebalance unless needed' do
- expect(IssueRebalancingWorker).not_to receive(:perform_async)
-
- expect(issue.relative_position).to eq(project.issues.maximum(:relative_position))
+ described_class.new(project, user, opts).execute
end
context 'when label belongs to project group' do
diff --git a/spec/services/issues/reorder_service_spec.rb b/spec/services/issues/reorder_service_spec.rb
index b6ad488a48c..78b937a1caf 100644
--- a/spec/services/issues/reorder_service_spec.rb
+++ b/spec/services/issues/reorder_service_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Issues::ReorderService do
- let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create_default(:user) }
let_it_be(:group) { create(:group) }
+ let_it_be(:project, reload: true) { create(:project, namespace: group) }
shared_examples 'issues reorder service' do
context 'when reordering issues' do
@@ -14,7 +14,7 @@ RSpec.describe Issues::ReorderService do
end
it 'returns false with both invalid params' do
- params = { move_after_id: nil, move_before_id: 1 }
+ params = { move_after_id: nil, move_before_id: non_existing_record_id }
expect(service(params).execute(issue1)).to be_falsey
end
@@ -27,27 +27,39 @@ RSpec.describe Issues::ReorderService do
expect(issue1.relative_position)
.to be_between(issue2.relative_position, issue3.relative_position)
end
+
+ it 'sorts issues if only given one neighbour, on the left' do
+ params = { move_before_id: issue3.id }
+
+ service(params).execute(issue1)
+
+ expect(issue1.relative_position).to be > issue3.relative_position
+ end
+
+ it 'sorts issues if only given one neighbour, on the right' do
+ params = { move_after_id: issue1.id }
+
+ service(params).execute(issue3)
+
+ expect(issue3.relative_position).to be < issue1.relative_position
+ end
end
end
describe '#execute' do
- let(:issue1) { create(:issue, project: project, relative_position: 10) }
- let(:issue2) { create(:issue, project: project, relative_position: 20) }
- let(:issue3) { create(:issue, project: project, relative_position: 30) }
+ let_it_be(:issue1, reload: true) { create(:issue, project: project, relative_position: 10) }
+ let_it_be(:issue2) { create(:issue, project: project, relative_position: 20) }
+ let_it_be(:issue3, reload: true) { create(:issue, project: project, relative_position: 30) }
context 'when ordering issues in a project' do
- let(:parent) { project }
-
before do
- parent.add_developer(user)
+ project.add_developer(user)
end
it_behaves_like 'issues reorder service'
end
context 'when ordering issues in a group' do
- let(:project) { create(:project, namespace: group) }
-
before do
group.add_developer(user)
end
diff --git a/spec/workers/issue_placement_worker_spec.rb b/spec/workers/issue_placement_worker_spec.rb
new file mode 100644
index 00000000000..05d1ab8496e
--- /dev/null
+++ b/spec/workers/issue_placement_worker_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe IssuePlacementWorker do
+ describe '#perform' do
+ let_it_be(:time) { Time.now.utc }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:author) { create(:user) }
+ let_it_be(:common_attrs) { { author: author, project: project } }
+ let_it_be(:unplaced) { common_attrs.merge(relative_position: nil) }
+ let_it_be(:issue) { create(:issue, **unplaced, created_at: time) }
+ let_it_be(:issue_a) { create(:issue, **unplaced, created_at: time - 1.minute) }
+ let_it_be(:issue_b) { create(:issue, **unplaced, created_at: time - 2.minutes) }
+ let_it_be(:issue_c) { create(:issue, **unplaced, created_at: time + 1.minute) }
+ let_it_be(:issue_d) { create(:issue, **unplaced, created_at: time + 2.minutes) }
+ let_it_be(:issue_e) { create(:issue, **common_attrs, relative_position: 10, created_at: time + 1.minute) }
+ let_it_be(:issue_f) { create(:issue, **unplaced, created_at: time + 1.minute) }
+
+ let_it_be(:irrelevant) { create(:issue, relative_position: nil, created_at: time) }
+
+ it 'places all issues created at most 5 minutes before this one at the end, most recent last' do
+ expect do
+ described_class.new.perform(issue.id)
+ end.not_to change { irrelevant.reset.relative_position }
+
+ expect(project.issues.order_relative_position_asc)
+ .to eq([issue_e, issue_b, issue_a, issue, issue_c, issue_f, issue_d])
+ expect(project.issues.where(relative_position: nil)).not_to exist
+ end
+
+ it 'schedules rebalancing if needed' do
+ issue_a.update!(relative_position: RelativePositioning::MAX_POSITION)
+
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
+
+ described_class.new.perform(issue.id)
+ end
+
+ it 'limits the sweep to QUERY_LIMIT records' do
+ # Ensure there are more than N issues in this set
+ n = described_class::QUERY_LIMIT
+ create_list(:issue, n - 5, **unplaced)
+
+ expect(Issue).to receive(:move_nulls_to_end).with(have_attributes(count: n)).and_call_original
+
+ described_class.new.perform(issue.id)
+
+ expect(project.issues.where(relative_position: nil)).to exist
+ end
+
+ it 'anticipates the failure to find the issue' do
+ id = non_existing_record_id
+
+ expect { described_class.new.perform(id) }.not_to raise_error
+ end
+
+ it 'anticipates the failure to place the issues, and schedules rebalancing' do
+ allow(Issue).to receive(:move_nulls_to_end) { raise RelativePositioning::NoSpaceLeft }
+
+ expect(IssueRebalancingWorker).to receive(:perform_async).with(nil, project.id)
+ expect(Gitlab::ErrorTracking)
+ .to receive(:log_exception)
+ .with(RelativePositioning::NoSpaceLeft, issue_id: issue.id)
+
+ described_class.new.perform(issue.id)
+ end
+ end
+end
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index 6386af8d253..d56732770a3 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -39,10 +39,10 @@ RSpec.describe NewIssueWorker do
end
context 'when everything is ok' do
- let(:project) { create(:project, :public) }
- let(:mentioned) { create(:user) }
- let(:user) { create(:user) }
- let(:issue) { create(:issue, project: project, description: "issue for #{mentioned.to_reference}") }
+ let_it_be(:user) { create_default(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:mentioned) { create(:user) }
+ let_it_be(:issue) { create(:issue, project: project, description: "issue for #{mentioned.to_reference}") }
it 'creates a new event record' do
expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
diff --git a/yarn.lock b/yarn.lock
index 67f564fc6b6..74b63faa243 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1076,56 +1076,56 @@
resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.0.3-1.tgz#9b9eb8858a6507162911007d355d9a206e1c5caa"
integrity sha512-szFhWD+V5TAxVNVIG16klgq+ypqA5k5AecLarTTrXgOG8cawVbQdOAwLbCmzkwiQ60rGSxAFoC1u2LrzxSK2Aw==
-"@sentry/browser@^5.10.2":
- version "5.10.2"
- resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.10.2.tgz#0bbb05505c58ea998c833cffec3f922fe4b4fa58"
- integrity sha512-r3eyBu2ln7odvWtXARCZPzpuGrKsD6U9F3gKTu4xdFkA0swSLUvS7AC2FUksj/1BE23y+eB/zzPT+RYJ58tidA==
- dependencies:
- "@sentry/core" "5.10.2"
- "@sentry/types" "5.10.0"
- "@sentry/utils" "5.10.2"
+"@sentry/browser@^5.22.3":
+ version "5.22.3"
+ resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.22.3.tgz#7a64bd1cf01bf393741a3e4bf35f82aa927f5b4e"
+ integrity sha512-2TzE/CoBa5ZkvxJizDdi1Iz1ldmXSJpFQ1mL07PIXBjCt0Wxf+WOuFSj5IP4L40XHfJE5gU8wEvSH0VDR8nXtA==
+ dependencies:
+ "@sentry/core" "5.22.3"
+ "@sentry/types" "5.22.3"
+ "@sentry/utils" "5.22.3"
tslib "^1.9.3"
-"@sentry/core@5.10.2":
- version "5.10.2"
- resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.10.2.tgz#1cb64489e6f8363c3249415b49d3f1289814825f"
- integrity sha512-sKVeFH3v8K8xw2vM5MKMnnyAAwih+JSE3pbNL0CcCCA+/SwX+3jeAo2BhgXev2SAR/TjWW+wmeC9TdIW7KyYbg==
+"@sentry/core@5.22.3":
+ version "5.22.3"
+ resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.22.3.tgz#030f435f2b518f282ba8bd954dac90cd70888bd7"
+ integrity sha512-eGL5uUarw3o4i9QUb9JoFHnhriPpWCaqeaIBB06HUpdcvhrjoowcKZj1+WPec5lFg5XusE35vez7z/FPzmJUDw==
dependencies:
- "@sentry/hub" "5.10.2"
- "@sentry/minimal" "5.10.2"
- "@sentry/types" "5.10.0"
- "@sentry/utils" "5.10.2"
+ "@sentry/hub" "5.22.3"
+ "@sentry/minimal" "5.22.3"
+ "@sentry/types" "5.22.3"
+ "@sentry/utils" "5.22.3"
tslib "^1.9.3"
-"@sentry/hub@5.10.2":
- version "5.10.2"
- resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.10.2.tgz#25d9f36b8f7c5cb65cf486737fa61dc9bf69b7e3"
- integrity sha512-hSlZIiu3hcR/I5yEhlpN9C0nip+U7hiRzRzUQaBiHO4YG4TC58NqnOPR89D/ekiuHIXzFpjW9OQmqtAMRoSUYA==
+"@sentry/hub@5.22.3":
+ version "5.22.3"
+ resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.22.3.tgz#08309a70d2ea8d5e313d05840c1711f34f2fffe5"
+ integrity sha512-INo47m6N5HFEs/7GMP9cqxOIt7rmRxdERunA3H2L37owjcr77MwHVeeJ9yawRS6FMtbWXplgWTyTIWIYOuqVbw==
dependencies:
- "@sentry/types" "5.10.0"
- "@sentry/utils" "5.10.2"
+ "@sentry/types" "5.22.3"
+ "@sentry/utils" "5.22.3"
tslib "^1.9.3"
-"@sentry/minimal@5.10.2":
- version "5.10.2"
- resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.10.2.tgz#267c2f3aa6877a0fe7a86971942e83f3ee616580"
- integrity sha512-GalixiM9sckYfompH5HHTp9XT2BcjawBkcl1DMEKUBEi37+kUq0bivOBmnN1G/I4/wWOUdnAI/kagDWaWpbZPg==
+"@sentry/minimal@5.22.3":
+ version "5.22.3"
+ resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-5.22.3.tgz#706e4029ae5494123d3875c658ba8911aa5cc440"
+ integrity sha512-HoINpYnVYCpNjn2XIPIlqH5o4BAITpTljXjtAftOx6Hzj+Opjg8tR8PWliyKDvkXPpc4kXK9D6TpEDw8MO0wZA==
dependencies:
- "@sentry/hub" "5.10.2"
- "@sentry/types" "5.10.0"
+ "@sentry/hub" "5.22.3"
+ "@sentry/types" "5.22.3"
tslib "^1.9.3"
-"@sentry/types@5.10.0":
- version "5.10.0"
- resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.10.0.tgz#4f0ba31b6e4d5371112c38279f11f66c73b43746"
- integrity sha512-TW20GzkCWsP6uAxR2JIpIkiitCKyIOfkyDsKBeLqYj4SaZjfvBPnzgNCcYR0L0UsP1/Es6oHooZfIGSkp6GGxQ==
+"@sentry/types@5.22.3":
+ version "5.22.3"
+ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.22.3.tgz#d1d547b30ee8bd7771fa893af74c4f3d71f0fd18"
+ integrity sha512-cv+VWK0YFgCVDvD1/HrrBWOWYG3MLuCUJRBTkV/Opdy7nkdNjhCAJQrEyMM9zX0sac8FKWKOHT0sykNh8KgmYw==
-"@sentry/utils@5.10.2":
- version "5.10.2"
- resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.10.2.tgz#261f575079d30aaf604e59f5f4de0aa21db22252"
- integrity sha512-UcbbaFpYrGSV448lQ16Cr+W/MPuKUflQQUdrMCt5vgaf5+M7kpozlcji4GGGZUCXIA7oRP93ABoXj55s1OM9zw==
+"@sentry/utils@5.22.3":
+ version "5.22.3"
+ resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.22.3.tgz#e3bda3e789239eb16d436f768daa12829f33d18f"
+ integrity sha512-AHNryXMBvIkIE+GQxTlmhBXD0Ksh+5w1SwM5qi6AttH+1qjWLvV6WB4+4pvVvEoS8t5F+WaVUZPQLmCCWp6zKw==
dependencies:
- "@sentry/types" "5.10.0"
+ "@sentry/types" "5.22.3"
tslib "^1.9.3"
"@sindresorhus/is@^0.14.0":