{
- if (!gon.features?.labelsWidget && !$block.hasClass('labels-select-wrapper')) {
- $block.find('.js-sidebar-dropdown-toggle').trigger('click');
- }
- }, DEBOUNCE_DROPDOWN_DELAY);
};
Sidebar.prototype.setCollapseAfterUpdate = function ($block) {
diff --git a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
index 5cd4a1a5192..29e8cabac30 100644
--- a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
+++ b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
@@ -1,152 +1,24 @@
{{ __('None') }}
-
- {{ __('None') }}
-
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue
index 4c07cf44fed..f93415ced45 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue
@@ -26,6 +26,11 @@ export default {
type: String,
required: true,
},
+ buttonClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
buttonTestid: {
type: String,
required: false,
@@ -39,7 +44,7 @@ export default {
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 3152c4d733f..3fbcb2fd7aa 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -11,7 +11,6 @@ class Groups::BoardsController < Groups::ApplicationController
push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml)
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml)
- push_frontend_feature_flag(:labels_widget, group, default_enabled: :yaml)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.use { }
e.try { }
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 7354c2c71ac..81ad6243efe 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -11,7 +11,6 @@ class Projects::BoardsController < Projects::ApplicationController
push_frontend_feature_flag(:issue_boards_filtered_search, project, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
- push_frontend_feature_flag(:labels_widget, project, default_enabled: :yaml)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.use { }
e.try { }
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 853e9c7ccdd..345e4434f4d 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -51,7 +51,6 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:real_time_issue_sidebar, @project, default_enabled: :yaml)
push_frontend_feature_flag(:confidential_notes, @project, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
- push_frontend_feature_flag(:labels_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
experiment(:invite_members_in_comment, namespace: @project.root_ancestor) do |experiment_instance|
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 6c5a8aa0610..aca556b18cb 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -42,7 +42,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:restructured_mr_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
- push_frontend_feature_flag(:labels_widget, project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
diff --git a/config/feature_flags/development/labels_widget.yml b/config/feature_flags/development/log_implicit_sidekiq_status_calls.yml
similarity index 61%
rename from config/feature_flags/development/labels_widget.yml
rename to config/feature_flags/development/log_implicit_sidekiq_status_calls.yml
index 07045a13c30..1aeb768b3dd 100644
--- a/config/feature_flags/development/labels_widget.yml
+++ b/config/feature_flags/development/log_implicit_sidekiq_status_calls.yml
@@ -1,8 +1,8 @@
---
-name: labels_widget
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62898
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332327
-milestone: '14.0'
+name: log_implicit_sidekiq_status_calls
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74815
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343964
+milestone: '14.6'
type: development
-group: group::project management
+group: group::scalability
default_enabled: false
diff --git a/db/post_migrate/20211130201100_track_deletions_in_namespaces.rb b/db/post_migrate/20211130201100_track_deletions_in_namespaces.rb
new file mode 100644
index 00000000000..e688e0b467d
--- /dev/null
+++ b/db/post_migrate/20211130201100_track_deletions_in_namespaces.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class TrackDeletionsInNamespaces < Gitlab::Database::Migration[1.0]
+ include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
+
+ enable_lock_retries!
+
+ def up
+ track_record_deletions(:namespaces)
+ end
+
+ def down
+ untrack_record_deletions(:namespaces)
+ end
+end
diff --git a/db/post_migrate/20211130201101_track_deletions_in_projects.rb b/db/post_migrate/20211130201101_track_deletions_in_projects.rb
new file mode 100644
index 00000000000..dfe7ab4c037
--- /dev/null
+++ b/db/post_migrate/20211130201101_track_deletions_in_projects.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class TrackDeletionsInProjects < Gitlab::Database::Migration[1.0]
+ include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
+
+ enable_lock_retries!
+
+ def up
+ track_record_deletions(:projects)
+ end
+
+ def down
+ untrack_record_deletions(:projects)
+ end
+end
diff --git a/db/schema_migrations/20211130201100 b/db/schema_migrations/20211130201100
new file mode 100644
index 00000000000..80c2d68671b
--- /dev/null
+++ b/db/schema_migrations/20211130201100
@@ -0,0 +1 @@
+cc0146769929c9fbb0b7b6788826d2e188c8664a14e1015563ba4f9e65397c4e
\ No newline at end of file
diff --git a/db/schema_migrations/20211130201101 b/db/schema_migrations/20211130201101
new file mode 100644
index 00000000000..ef1178c10c5
--- /dev/null
+++ b/db/schema_migrations/20211130201101
@@ -0,0 +1 @@
+b0215ac45031593ca98de4f8858d21f1c29af03742a422bffd83598e39a6871c
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index b8c75e33e7b..a932ea30de5 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -28729,6 +28729,10 @@ CREATE TRIGGER ci_pipelines_loose_fk_trigger AFTER DELETE ON ci_pipelines REFERE
CREATE TRIGGER ci_runners_loose_fk_trigger AFTER DELETE ON ci_runners REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
+CREATE TRIGGER namespaces_loose_fk_trigger AFTER DELETE ON namespaces REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
+
+CREATE TRIGGER projects_loose_fk_trigger AFTER DELETE ON projects REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
+
CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace();
CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON integrations FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_issue_tracker();
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 754e6c7aec6..e3b8f7372b9 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -171,7 +171,10 @@ we should track our progress through the exception list.
When auto-generating the `.rubocop_todo.yml` exception list for a particular Cop,
and more than 15 files are affected, we should add the exception list to
-a different file, `.rubocop_manual_todo.yml`.
+a different file within `.rubocop_todo/` directory.
+
+For example, the configuration for the cop `Gitlab/NamespacedClass` is located
+in `.rubocop_todo/gitlab/namespaced_class.yml`.
This ensures that our list isn't mistakenly removed by another auto generation of
the `.rubocop_todo.yml`. This also allows us greater visibility into the exceptions
@@ -184,19 +187,19 @@ bundle exec rake rubocop:todo:generate
```
You can then move the list from the freshly generated `.rubocop_todo.yml` for the Cop being actively
-resolved and place it in the `.rubocop_manual_todo.yml`. In this scenario, do not commit auto generated
-changes to the `.rubocop_todo.yml` as an `exclude limit` that is higher than 15 will make the
-`.rubocop_todo.yml` hard to parse.
+resolved and place it in the directory `.rubocop_todo/`. In this scenario, do not commit
+auto-generated changes to the `.rubocop_todo.yml`, as an `exclude limit` that is higher than 15
+makes the `.rubocop_todo.yml` hard to parse.
### Reveal existing RuboCop exceptions
To reveal existing RuboCop exceptions in the code that have been excluded via `.rubocop_todo.yml` and
-`.rubocop_manual_todo.yml`, set the environment variable `REVEAL_RUBOCOP_TODO` to `1`.
+`.rubocop_todo/**/*.yml`, set the environment variable `REVEAL_RUBOCOP_TODO` to `1`.
This allows you to reveal existing RuboCop exceptions during your daily work cycle and fix them along the way.
NOTE:
-Permanent `Exclude`s should be defined in `.rubocop.yml` instead of `.rubocop_manual_todo.yml`.
+Define permanent `Exclude`s in `.rubocop.yml` instead of `.rubocop_todo/**/*.yml`.
## Database migrations
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 2c536c441e4..cfdb3ad0d62 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -36,6 +36,12 @@ On every [pipeline](https://gitlab.com/gitlab-org/gitlab/pipelines/125315730) in
browser performance testing using a
[Sitespeed.io Container](../../user/project/merge_requests/browser_performance_testing.md).
+## Sample Data for Review Apps
+
+Upon deployment of a review app, project data is created from the [`sample-gitlab-project`](https://gitlab.com/gitlab-org/sample-data-templates/sample-gitlab-project) template project. This aims to provide projects with prepopulated resources to facilitate manual and exploratory testing.
+
+The sample projects will be created in the `root` user namespace and can be accessed from the personal projects list for that user.
+
## How to
### Redeploy Review App from a clean slate
diff --git a/doc/user/project/merge_requests/creating_merge_requests.md b/doc/user/project/merge_requests/creating_merge_requests.md
index e15874d2df8..220049d9a88 100644
--- a/doc/user/project/merge_requests/creating_merge_requests.md
+++ b/doc/user/project/merge_requests/creating_merge_requests.md
@@ -159,6 +159,8 @@ branch already exists, the patches are applied on top of it.
## Set the default target project
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58093) in GitLab 13.11.
+
Merge requests have a source and a target project that are the same, unless
forking is involved. Creating a fork of the project can cause either of these
scenarios when you create a new merge request:
diff --git a/lib/api/entities/project_import_failed_relation.rb b/lib/api/entities/project_import_failed_relation.rb
index b8f842c1646..26cfae7260c 100644
--- a/lib/api/entities/project_import_failed_relation.rb
+++ b/lib/api/entities/project_import_failed_relation.rb
@@ -10,6 +10,7 @@ module API
end
expose :relation_key, as: :relation_name
+ expose :relation_index, as: :line_number
end
end
end
diff --git a/lib/api/entities/project_import_status.rb b/lib/api/entities/project_import_status.rb
index e79c1cdf1a2..b6bcac16ff3 100644
--- a/lib/api/entities/project_import_status.rb
+++ b/lib/api/entities/project_import_status.rb
@@ -4,6 +4,7 @@ module API
module Entities
class ProjectImportStatus < ProjectIdentity
expose :import_status
+ expose :import_type
expose :correlation_id do |project, _options|
project.import_state&.correlation_id
end
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index e7c532e2483..56b4f4c6598 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -107,7 +107,7 @@ module API
params do
requires :id, type: String, desc: 'The ID of a project'
end
- desc 'Get a project export status' do
+ desc 'Get a project import status' do
detail 'This feature was introduced in GitLab 10.6.'
success Entities::ProjectImportStatus
end
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index fbf2718d718..120d18f63f2 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -29,13 +29,16 @@ module Gitlab
# for most jobs.
DEFAULT_EXPIRATION = 30.minutes.to_i
+ DEFAULT_VALUE = 1
+ DEFAULT_VALUE_MESSAGE = 'Keys using the default value for SidekiqStatus detected'
+
# Starts tracking of the given job.
#
# jid - The Sidekiq job ID
# expire - The expiration time of the Redis key.
- def self.set(jid, expire = DEFAULT_EXPIRATION)
+ def self.set(jid, expire = DEFAULT_EXPIRATION, value: DEFAULT_VALUE)
Sidekiq.redis do |redis|
- redis.set(key_for(jid), 1, ex: expire)
+ redis.set(key_for(jid), value, ex: expire)
end
end
@@ -88,13 +91,20 @@ module Gitlab
# true = job is still running or enqueued
# false = job completed
def self.job_status(job_ids)
- keys = job_ids.map { |jid| key_for(jid) }
+ return [] if job_ids.empty?
- Sidekiq.redis do |redis|
- redis.pipelined do
- keys.each { |key| redis.exists(key) }
- end
+ keys = job_ids.map { |jid| key_for(jid) }
+ results = Sidekiq.redis { |redis| redis.mget(*keys) }
+
+ if Feature.enabled?(:log_implicit_sidekiq_status_calls, default_enabled: :yaml)
+ to_log = keys.zip(results).select do |_key, result|
+ result == DEFAULT_VALUE.to_s
+ end.map(&:first)
+
+ Sidekiq.logger.info(message: DEFAULT_VALUE_MESSAGE, keys: to_log) if to_log.any?
end
+
+ results.map { |result| !result.nil? }
end
# Returns the JIDs that are completed
diff --git a/lib/gitlab/sidekiq_status/client_middleware.rb b/lib/gitlab/sidekiq_status/client_middleware.rb
index bfd5038557d..cee7270f2fb 100644
--- a/lib/gitlab/sidekiq_status/client_middleware.rb
+++ b/lib/gitlab/sidekiq_status/client_middleware.rb
@@ -5,8 +5,9 @@ module Gitlab
class ClientMiddleware
def call(_, job, _, _)
status_expiration = job['status_expiration'] || Gitlab::SidekiqStatus::DEFAULT_EXPIRATION
+ value = job['status_expiration'] ? 2 : Gitlab::SidekiqStatus::DEFAULT_VALUE
- Gitlab::SidekiqStatus.set(job['jid'], status_expiration)
+ Gitlab::SidekiqStatus.set(job['jid'], status_expiration, value: value)
yield
end
end
diff --git a/qa/qa/page/component/issuable/sidebar.rb b/qa/qa/page/component/issuable/sidebar.rb
index 77962570aed..cdfd7fbdad9 100644
--- a/qa/qa/page/component/issuable/sidebar.rb
+++ b/qa/qa/page/component/issuable/sidebar.rb
@@ -124,15 +124,6 @@ module QA
click_element(:more_assignees_link)
end
- # When the labels_widget feature flag is enabled, wait until the labels widget appears
- def wait_for_labels_widget_feature_flag
- Support::Retrier.retry_until(max_duration: 60, reload_page: page, retry_on_exception: true, sleep_interval: 5) do
- within_element(:labels_block) do
- find_element(:edit_link)
- end
- end
- end
-
private
def wait_assignees_block_finish_loading
diff --git a/spec/factories/import_failures.rb b/spec/factories/import_failures.rb
index 376b2ff39e2..df0793664f4 100644
--- a/spec/factories/import_failures.rb
+++ b/spec/factories/import_failures.rb
@@ -10,6 +10,8 @@ FactoryBot.define do
exception_class { 'RuntimeError' }
exception_message { 'Something went wrong' }
source { 'method_call' }
+ relation_key { 'issues' }
+ relation_index { 1 }
correlation_id_value { SecureRandom.uuid }
trait :hard_failure do
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index b2e7ac9cb0b..10b0082f65a 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -15,7 +15,6 @@ RSpec.describe "Issues > User edits issue", :js do
context 'with authorized user' do
before do
- stub_feature_flags(labels_widget: false)
project.add_developer(user)
project_with_milestones.add_developer(user)
sign_in(user)
@@ -151,7 +150,7 @@ RSpec.describe "Issues > User edits issue", :js do
page.within '.block.labels' do
# Remove `verisimilitude` label
within '.gl-label' do
- click_button
+ click_button 'Remove label'
end
expect(page).to have_text('syzygy')
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 25c315f2d16..b5f414e39e4 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -17,7 +17,6 @@ RSpec.describe 'Labels Hierarchy', :js do
let!(:project_label_1) { create(:label, project: project_1, title: 'Label_4') }
before do
- stub_feature_flags(labels_widget: false)
grandparent.add_owner(user)
sign_in(user)
@@ -28,16 +27,15 @@ RSpec.describe 'Labels Hierarchy', :js do
[grandparent_group_label, parent_group_label, project_label_1].each do |label|
page.within('.block.labels') do
click_on 'Edit'
+
+ wait_for_requests
+
+ click_on label.title
+ click_on 'Close'
end
wait_for_requests
- find('a.label-item', text: label.title).click
- wait_for_requests
- click_on 'Close'
-
- wait_for_requests
-
expect(page).to have_selector('.gl-label', text: label.title)
end
end
diff --git a/spec/frontend/boards/components/board_content_sidebar_spec.js b/spec/frontend/boards/components/board_content_sidebar_spec.js
index 8a8250205d0..7b176cea2a3 100644
--- a/spec/frontend/boards/components/board_content_sidebar_spec.js
+++ b/spec/frontend/boards/components/board_content_sidebar_spec.js
@@ -1,18 +1,20 @@
import { GlDrawer } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { MountingPortal } from 'portal-vue';
+import Vue from 'vue';
import Vuex from 'vuex';
import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue';
import { stubComponent } from 'helpers/stub_component';
import BoardContentSidebar from '~/boards/components/board_content_sidebar.vue';
-import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
+import SidebarLabelsWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { mockActiveIssue, mockIssue, mockIssueGroupPath, mockIssueProjectPath } from '../mock_data';
+Vue.use(Vuex);
describe('BoardContentSidebar', () => {
let wrapper;
let store;
@@ -32,6 +34,7 @@ describe('BoardContentSidebar', () => {
groupPathForActiveIssue: () => mockIssueGroupPath,
projectPathForActiveIssue: () => mockIssueProjectPath,
isSidebarOpen: () => true,
+ isGroupBoard: () => false,
...mockGetters,
},
actions: mockActions,
@@ -115,8 +118,8 @@ describe('BoardContentSidebar', () => {
expect(wrapper.findComponent(SidebarTodoWidget).exists()).toBe(true);
});
- it('renders BoardSidebarLabelsSelect', () => {
- expect(wrapper.findComponent(BoardSidebarLabelsSelect).exists()).toBe(true);
+ it('renders SidebarLabelsWidget', () => {
+ expect(wrapper.findComponent(SidebarLabelsWidget).exists()).toBe(true);
});
it('renders BoardSidebarTitle', () => {
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index e245325b956..f580469b072 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -1570,7 +1570,7 @@ describe('addListNewIssue', () => {
describe('setActiveIssueLabels', () => {
const state = { boardItems: { [mockIssue.id]: mockIssue } };
- const getters = { activeBoardItem: mockIssue };
+ const getters = { activeBoardItem: { ...mockIssue, labels } };
const testLabelIds = labels.map((label) => label.id);
const input = {
labelIds: testLabelIds,
@@ -1579,11 +1579,7 @@ describe('setActiveIssueLabels', () => {
labels,
};
- it('should assign labels on success', (done) => {
- jest
- .spyOn(gqlClient, 'mutate')
- .mockResolvedValue({ data: { updateIssue: { issue: { labels: { nodes: labels } } } } });
-
+ it('should assign labels', () => {
const payload = {
itemId: getters.activeBoardItem.id,
prop: 'labels',
@@ -1601,74 +1597,28 @@ describe('setActiveIssueLabels', () => {
},
],
[],
- done,
);
});
- it('throws error if fails', async () => {
- jest
- .spyOn(gqlClient, 'mutate')
- .mockResolvedValue({ data: { updateIssue: { errors: ['failed mutation'] } } });
+ it('should remove label', () => {
+ const payload = {
+ itemId: getters.activeBoardItem.id,
+ prop: 'labels',
+ value: [labels[1]],
+ };
- await expect(actions.setActiveIssueLabels({ getters }, input)).rejects.toThrow(Error);
- });
-
- describe('labels_widget FF on', () => {
- beforeEach(() => {
- window.gon = {
- features: { labelsWidget: true },
- };
-
- getters.activeBoardItem = { ...mockIssue, labels };
- });
-
- afterEach(() => {
- window.gon = {
- features: {},
- };
- });
-
- it('should assign labels', () => {
- const payload = {
- itemId: getters.activeBoardItem.id,
- prop: 'labels',
- value: labels,
- };
-
- testAction(
- actions.setActiveIssueLabels,
- input,
- { ...state, ...getters },
- [
- {
- type: types.UPDATE_BOARD_ITEM_BY_ID,
- payload,
- },
- ],
- [],
- );
- });
-
- it('should remove label', () => {
- const payload = {
- itemId: getters.activeBoardItem.id,
- prop: 'labels',
- value: [labels[1]],
- };
-
- testAction(
- actions.setActiveIssueLabels,
- { ...input, removeLabelIds: [getIdFromGraphQLId(labels[0].id)] },
- { ...state, ...getters },
- [
- {
- type: types.UPDATE_BOARD_ITEM_BY_ID,
- payload,
- },
- ],
- [],
- );
- });
+ testAction(
+ actions.setActiveIssueLabels,
+ { ...input, removeLabelIds: [getIdFromGraphQLId(labels[0].id)] },
+ { ...state, ...getters },
+ [
+ {
+ type: types.UPDATE_BOARD_ITEM_BY_ID,
+ payload,
+ },
+ ],
+ [],
+ );
});
});
diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js
index f7bde8d2f16..6cccdd2989f 100644
--- a/spec/frontend/flash_spec.js
+++ b/spec/frontend/flash_spec.js
@@ -1,38 +1,12 @@
import createFlash, {
- createFlashEl,
createAction,
hideFlash,
removeFlashClickListener,
+ FLASH_TYPES,
FLASH_CLOSED_EVENT,
} from '~/flash';
describe('Flash', () => {
- describe('createFlashEl', () => {
- let el;
-
- beforeEach(() => {
- el = document.createElement('div');
- });
-
- afterEach(() => {
- el.innerHTML = '';
- });
-
- it('creates flash element with type', () => {
- el.innerHTML = createFlashEl('testing', 'alert');
-
- expect(el.querySelector('.flash-alert')).not.toBeNull();
- });
-
- it('escapes text', () => {
- el.innerHTML = createFlashEl('', 'alert');
-
- expect(el.querySelector('.flash-text').textContent.trim()).toBe(
- '',
- );
- });
- });
-
describe('hideFlash', () => {
let el;
@@ -137,14 +111,10 @@ describe('Flash', () => {
describe('createFlash', () => {
const message = 'test';
- const type = 'alert';
- const parent = document;
const fadeTransition = false;
const addBodyClass = true;
const defaultParams = {
message,
- type,
- parent,
actionConfig: null,
fadeTransition,
addBodyClass,
@@ -171,14 +141,28 @@ describe('Flash', () => {
document.querySelector('.js-content-wrapper').remove();
});
- it('adds flash element into container', () => {
+ it('adds flash alert element into the document by default', () => {
createFlash({ ...defaultParams });
- expect(document.querySelector('.flash-alert')).not.toBeNull();
-
+ expect(document.querySelector('.flash-container .flash-alert')).not.toBeNull();
expect(document.body.className).toContain('flash-shown');
});
+ it('adds flash of a warning type', () => {
+ createFlash({ ...defaultParams, type: FLASH_TYPES.WARNING });
+
+ expect(document.querySelector('.flash-container .flash-warning')).not.toBeNull();
+ expect(document.body.className).toContain('flash-shown');
+ });
+
+ it('escapes text', () => {
+ createFlash({ ...defaultParams, message: '' });
+
+ expect(document.querySelector('.flash-text').textContent.trim()).toBe(
+ '',
+ );
+ });
+
it('adds flash into specified parent', () => {
createFlash({ ...defaultParams, parent: document.querySelector('.content-wrapper') });
diff --git a/spec/frontend/sidebar/sidebar_labels_spec.js b/spec/frontend/sidebar/sidebar_labels_spec.js
index 8437ee1b723..825a930100b 100644
--- a/spec/frontend/sidebar/sidebar_labels_spec.js
+++ b/spec/frontend/sidebar/sidebar_labels_spec.js
@@ -1,60 +1,22 @@
import { shallowMount } from '@vue/test-utils';
-import {
- mockLabels,
- mockRegularLabel,
-} from 'jest/vue_shared/components/sidebar/labels_select_vue/mock_data';
-import updateIssueLabelsMutation from '~/boards/graphql/issue_set_labels.mutation.graphql';
-import { MutationOperationMode } from '~/graphql_shared/utils';
-import { IssuableType } from '~/issue_show/constants';
import SidebarLabels from '~/sidebar/components/labels/sidebar_labels.vue';
-import updateMergeRequestLabelsMutation from '~/sidebar/queries/update_merge_request_labels.mutation.graphql';
-import { toLabelGid } from '~/sidebar/utils';
-import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
-import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue';
+import {
+ DropdownVariant,
+ LabelType,
+} from '~/vue_shared/components/sidebar/labels_select_widget/constants';
+import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
describe('sidebar labels', () => {
let wrapper;
const defaultProps = {
- allowLabelCreate: true,
allowLabelEdit: true,
- allowScopedLabels: true,
- canEdit: true,
iid: '1',
- initiallySelectedLabels: mockLabels,
issuableType: 'issue',
- labelsFetchPath: '/gitlab-org/gitlab-test/-/labels.json?include_ancestor_groups=true',
- labelsManagePath: '/gitlab-org/gitlab-test/-/labels',
projectIssuesPath: '/gitlab-org/gitlab-test/-/issues',
- projectPath: 'gitlab-org/gitlab-test',
fullPath: 'gitlab-org/gitlab-test',
};
- const $apollo = {
- mutate: jest.fn().mockResolvedValue(),
- };
-
- const userUpdatedLabels = [
- {
- ...mockRegularLabel,
- set: false,
- },
- {
- id: 40,
- title: 'Security',
- color: '#ddd',
- text_color: '#fff',
- set: true,
- },
- {
- id: 55,
- title: 'Tooling',
- color: '#ddd',
- text_color: '#fff',
- set: false,
- },
- ];
-
const findLabelsSelect = () => wrapper.find(LabelsSelect);
const mountComponent = (props = {}) => {
@@ -63,9 +25,6 @@ describe('sidebar labels', () => {
...defaultProps,
...props,
},
- mocks: {
- $apollo,
- },
});
};
@@ -75,115 +34,31 @@ describe('sidebar labels', () => {
});
describe('LabelsSelect props', () => {
- beforeEach(() => {
- mountComponent();
- });
-
- it('are as expected', () => {
- expect(findLabelsSelect().props()).toMatchObject({
- allowLabelCreate: defaultProps.allowLabelCreate,
- allowLabelEdit: defaultProps.allowLabelEdit,
- allowMultiselect: true,
- allowScopedLabels: defaultProps.allowScopedLabels,
- footerCreateLabelTitle: 'Create project label',
- footerManageLabelTitle: 'Manage project labels',
- labelsCreateTitle: 'Create project label',
- labelsFetchPath: defaultProps.labelsFetchPath,
- labelsFilterBasePath: defaultProps.projectIssuesPath,
- labelsManagePath: defaultProps.labelsManagePath,
- labelsSelectInProgress: false,
- selectedLabels: defaultProps.initiallySelectedLabels,
- variant: DropdownVariant.Sidebar,
+ describe.each`
+ issuableType
+ ${'issue'}
+ ${'merge_request'}
+ `('issuableType $issuableType', ({ issuableType }) => {
+ beforeEach(() => {
+ mountComponent({ issuableType });
});
- });
- });
- describe('when type is issue', () => {
- beforeEach(() => {
- mountComponent({ issuableType: IssuableType.Issue });
- });
-
- describe('when labels are updated', () => {
- it('invokes a mutation', () => {
- findLabelsSelect().vm.$emit('updateSelectedLabels', userUpdatedLabels);
-
- const expected = {
- mutation: updateIssueLabelsMutation,
- variables: {
- input: {
- iid: defaultProps.iid,
- projectPath: defaultProps.projectPath,
- labelIds: [toLabelGid(29), toLabelGid(28), toLabelGid(27), toLabelGid(40)],
- },
- },
- };
-
- expect($apollo.mutate).toHaveBeenCalledWith(expected);
- });
- });
-
- describe('when label `x` is clicked', () => {
- it('invokes a mutation', () => {
- findLabelsSelect().vm.$emit('onLabelRemove', 27);
-
- const expected = {
- mutation: updateIssueLabelsMutation,
- variables: {
- input: {
- iid: defaultProps.iid,
- projectPath: defaultProps.projectPath,
- removeLabelIds: [27],
- },
- },
- };
-
- expect($apollo.mutate).toHaveBeenCalledWith(expected);
- });
- });
- });
-
- describe('when type is merge_request', () => {
- beforeEach(() => {
- mountComponent({ issuableType: IssuableType.MergeRequest });
- });
-
- describe('when labels are updated', () => {
- it('invokes a mutation', () => {
- findLabelsSelect().vm.$emit('updateSelectedLabels', userUpdatedLabels);
-
- const expected = {
- mutation: updateMergeRequestLabelsMutation,
- variables: {
- input: {
- iid: defaultProps.iid,
- labelIds: [toLabelGid(29), toLabelGid(28), toLabelGid(27), toLabelGid(40)],
- operationMode: MutationOperationMode.Replace,
- projectPath: defaultProps.projectPath,
- },
- },
- };
-
- expect($apollo.mutate).toHaveBeenCalledWith(expected);
- });
- });
-
- describe('when label `x` is clicked', () => {
- it('invokes a mutation', () => {
- findLabelsSelect().vm.$emit('onLabelRemove', 27);
-
- const expected = {
- mutation: updateMergeRequestLabelsMutation,
- variables: {
- input: {
- iid: defaultProps.iid,
- labelIds: [toLabelGid(27)],
- operationMode: MutationOperationMode.Remove,
- projectPath: defaultProps.projectPath,
- },
- },
- };
-
- expect($apollo.mutate).toHaveBeenCalledWith(expected);
+ it('has expected props', () => {
+ expect(findLabelsSelect().props()).toMatchObject({
+ iid: defaultProps.iid,
+ fullPath: defaultProps.fullPath,
+ allowLabelRemove: defaultProps.allowLabelEdit,
+ allowMultiselect: true,
+ footerCreateLabelTitle: 'Create project label',
+ footerManageLabelTitle: 'Manage project labels',
+ labelsCreateTitle: 'Create project label',
+ labelsFilterBasePath: defaultProps.projectIssuesPath,
+ variant: DropdownVariant.Sidebar,
+ issuableType,
+ workspaceType: 'project',
+ attrWorkspacePath: defaultProps.fullPath,
+ labelCreateType: LabelType.project,
+ });
});
});
});
diff --git a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js
index 220f897c035..af7f85769aa 100644
--- a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js
+++ b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_spec.js
@@ -9,6 +9,7 @@ describe('Confirm Danger Modal', () => {
const phrase = 'En Taro Adun';
const buttonText = 'Click me!';
+ const buttonClass = 'gl-w-full';
const modalId = CONFIRM_DANGER_MODAL_ID;
const findBtn = () => wrapper.findComponent(GlButton);
@@ -19,6 +20,7 @@ describe('Confirm Danger Modal', () => {
shallowMountExtended(ConfirmDanger, {
propsData: {
buttonText,
+ buttonClass,
phrase,
...props,
},
@@ -51,6 +53,10 @@ describe('Confirm Danger Modal', () => {
expect(findBtn().attributes('disabled')).toBe('true');
});
+ it('passes `buttonClass` prop to button', () => {
+ expect(findBtn().classes()).toContain(buttonClass);
+ });
+
it('will emit `confirm` when the modal confirms', () => {
expect(wrapper.emitted('confirm')).toBeUndefined();
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
index d4203528874..7c3b7ae0ff5 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js
@@ -1,5 +1,5 @@
-import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -8,14 +8,14 @@ import { IssuableType } from '~/issue_show/constants';
import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue';
import DropdownContents from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue';
import DropdownValue from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue';
+import DropdownValueCollapsed from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_value_collapsed.vue';
import issueLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql';
import LabelsSelectRoot from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { mockConfig, issuableLabelsQueryResponse } from './mock_data';
jest.mock('~/flash');
-const localVue = createLocalVue();
-localVue.use(VueApollo);
+Vue.use(VueApollo);
const successfulQueryHandler = jest.fn().mockResolvedValue(issuableLabelsQueryResponse);
const errorQueryHandler = jest.fn().mockRejectedValue('Houston, we have a problem');
@@ -25,6 +25,7 @@ describe('LabelsSelectRoot', () => {
const findSidebarEditableItem = () => wrapper.findComponent(SidebarEditableItem);
const findDropdownValue = () => wrapper.findComponent(DropdownValue);
+ const findDropdownValueCollapsed = () => wrapper.findComponent(DropdownValueCollapsed);
const findDropdownContents = () => wrapper.findComponent(DropdownContents);
const createComponent = ({
@@ -37,7 +38,6 @@ describe('LabelsSelectRoot', () => {
wrapper = shallowMount(LabelsSelectRoot, {
slots,
apolloProvider: mockApollo,
- localVue,
propsData: {
...config,
issuableType: IssuableType.Issue,
@@ -107,6 +107,9 @@ describe('LabelsSelectRoot', () => {
expect(findDropdownValue().props('selectedLabels')).toEqual(
issuableLabelsQueryResponse.data.workspace.issuable.labels.nodes,
);
+ expect(findDropdownValueCollapsed().props('labels')).toEqual(
+ issuableLabelsQueryResponse.data.workspace.issuable.labels.nodes,
+ );
});
it('emits `onLabelRemove` event on dropdown value label remove event', () => {
diff --git a/spec/lib/api/entities/project_import_failed_relation_spec.rb b/spec/lib/api/entities/project_import_failed_relation_spec.rb
index d3c24f6fce3..d6143915ecb 100644
--- a/spec/lib/api/entities/project_import_failed_relation_spec.rb
+++ b/spec/lib/api/entities/project_import_failed_relation_spec.rb
@@ -16,7 +16,8 @@ RSpec.describe API::Entities::ProjectImportFailedRelation do
exception_class: import_failure.exception_class,
exception_message: nil,
relation_name: import_failure.relation_key,
- source: import_failure.source
+ source: import_failure.source,
+ line_number: import_failure.relation_index
)
end
end
diff --git a/spec/lib/api/entities/project_import_status_spec.rb b/spec/lib/api/entities/project_import_status_spec.rb
index 5eda613a6a6..f2cfaa25721 100644
--- a/spec/lib/api/entities/project_import_status_spec.rb
+++ b/spec/lib/api/entities/project_import_status_spec.rb
@@ -2,17 +2,18 @@
require 'spec_helper'
-RSpec.describe API::Entities::ProjectImportStatus do
+RSpec.describe API::Entities::ProjectImportStatus, :aggregate_failures do
describe '#as_json' do
subject { entity.as_json }
let(:correlation_id) { 'cid' }
context 'when no import state exists' do
- let(:entity) { described_class.new(build(:project)) }
+ let(:entity) { described_class.new(build(:project, import_type: 'import_type')) }
it 'includes basic fields and no failures' do
expect(subject[:import_status]).to eq('none')
+ expect(subject[:import_type]).to eq('import_type')
expect(subject[:correlation_id]).to be_nil
expect(subject[:import_error]).to be_nil
expect(subject[:failed_relations]).to eq([])
@@ -20,11 +21,12 @@ RSpec.describe API::Entities::ProjectImportStatus do
end
context 'when import has not finished yet' do
- let(:project) { create(:project, :import_scheduled, import_correlation_id: correlation_id) }
- let(:entity) { described_class.new(project) }
+ let(:project) { create(:project, :import_scheduled, import_type: 'import_type', import_correlation_id: correlation_id) }
+ let(:entity) { described_class.new(project, import_type: 'import_type') }
- it 'includes basic fields and no failures', :aggregate_failures do
+ it 'includes basic fields and no failures' do
expect(subject[:import_status]).to eq('scheduled')
+ expect(subject[:import_type]).to eq('import_type')
expect(subject[:correlation_id]).to eq(correlation_id)
expect(subject[:import_error]).to be_nil
expect(subject[:failed_relations]).to eq([])
@@ -32,25 +34,43 @@ RSpec.describe API::Entities::ProjectImportStatus do
end
context 'when import has finished with failed relations' do
- let(:project) { create(:project, :import_finished, import_correlation_id: correlation_id) }
+ let(:project) { create(:project, :import_finished, import_type: 'import_type', import_correlation_id: correlation_id) }
let(:entity) { described_class.new(project) }
- it 'includes basic fields with failed relations', :aggregate_failures do
- create(:import_failure, :hard_failure, project: project, correlation_id_value: correlation_id)
+ it 'includes basic fields with failed relations' do
+ create(
+ :import_failure,
+ :hard_failure,
+ project: project,
+ correlation_id_value: correlation_id,
+ relation_key: 'issues',
+ relation_index: 1
+ )
+
+ # Doesn't show soft failures
+ create(:import_failure, :soft_failure)
expect(subject[:import_status]).to eq('finished')
+ expect(subject[:import_type]).to eq('import_type')
expect(subject[:correlation_id]).to eq(correlation_id)
expect(subject[:import_error]).to be_nil
- expect(subject[:failed_relations]).not_to be_empty
+ expect(subject[:failed_relations].length).to eq(1)
+
+ failure = subject[:failed_relations].last
+ expect(failure[:exception_class]).to eq('RuntimeError')
+ expect(failure[:source]).to eq('method_call')
+ expect(failure[:relation_name]).to eq('issues')
+ expect(failure[:line_number]).to eq(1)
end
end
context 'when import has failed' do
- let(:project) { create(:project, :import_failed, import_correlation_id: correlation_id, import_last_error: 'error') }
+ let(:project) { create(:project, :import_failed, import_type: 'import_type', import_correlation_id: correlation_id, import_last_error: 'error') }
let(:entity) { described_class.new(project) }
- it 'includes basic fields with import error', :aggregate_failures do
+ it 'includes basic fields with import error' do
expect(subject[:import_status]).to eq('failed')
+ expect(subject[:import_type]).to eq('import_type')
expect(subject[:correlation_id]).to eq(correlation_id)
expect(subject[:import_error]).to eq('error')
expect(subject[:failed_relations]).to eq([])
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
index 0cf05fb0a5c..2f2499753b9 100644
--- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -1,14 +1,25 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe Gitlab::SidekiqStatus::ClientMiddleware do
describe '#call' do
- it 'tracks the job in Redis' do
- expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION)
+ context 'when the job has status_expiration set' do
+ it 'tracks the job in Redis with a value of 2' do
+ expect(Gitlab::SidekiqStatus).to receive(:set).with('123', 1.hour.to_i, value: 2)
- described_class.new
- .call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil }
+ described_class.new
+ .call('Foo', { 'jid' => '123', 'status_expiration' => 1.hour.to_i }, double(:queue), double(:pool)) { nil }
+ end
+ end
+
+ context 'when the job does not have status_expiration set' do
+ it 'tracks the job in Redis with a value of 1' do
+ expect(Gitlab::SidekiqStatus).to receive(:set).with('123', Gitlab::SidekiqStatus::DEFAULT_EXPIRATION, value: 1)
+
+ described_class.new
+ .call('Foo', { 'jid' => '123' }, double(:queue), double(:pool)) { nil }
+ end
end
end
end
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
index fc2ac29a1f9..1e7b52471b0 100644
--- a/spec/lib/gitlab/sidekiq_status_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -12,6 +12,31 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_
Sidekiq.redis do |redis|
expect(redis.exists(key)).to eq(true)
expect(redis.ttl(key) > 0).to eq(true)
+ expect(redis.get(key)).to eq(described_class::DEFAULT_VALUE.to_s)
+ end
+ end
+
+ it 'allows overriding the expiration time' do
+ described_class.set('123', described_class::DEFAULT_EXPIRATION * 2)
+
+ key = described_class.key_for('123')
+
+ Sidekiq.redis do |redis|
+ expect(redis.exists(key)).to eq(true)
+ expect(redis.ttl(key) > described_class::DEFAULT_EXPIRATION).to eq(true)
+ expect(redis.get(key)).to eq(described_class::DEFAULT_VALUE.to_s)
+ end
+ end
+
+ it 'allows overriding the default value' do
+ described_class.set('123', value: 2)
+
+ key = described_class.key_for('123')
+
+ Sidekiq.redis do |redis|
+ expect(redis.exists(key)).to eq(true)
+ expect(redis.ttl(key) > 0).to eq(true)
+ expect(redis.get(key)).to eq('2')
end
end
end
@@ -88,7 +113,7 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_
end
end
- describe 'completed' do
+ describe '.completed_jids' do
it 'returns the completed job' do
expect(described_class.completed_jids(%w(123))).to eq(['123'])
end
@@ -100,4 +125,46 @@ RSpec.describe Gitlab::SidekiqStatus, :clean_gitlab_redis_queues, :clean_gitlab_
expect(described_class.completed_jids(%w(123 456 789))).to eq(['789'])
end
end
+
+ describe '.job_status' do
+ it 'returns an array of boolean values' do
+ described_class.set('123')
+ described_class.set('456')
+ described_class.unset('123')
+
+ expect(described_class.job_status(%w(123 456 789))).to eq([false, true, false])
+ end
+
+ it 'handles an empty array' do
+ expect(described_class.job_status([])).to eq([])
+ end
+
+ context 'when log_implicit_sidekiq_status_calls is enabled' do
+ it 'logs keys that contained the default value' do
+ described_class.set('123', value: 2)
+ described_class.set('456')
+ described_class.set('012')
+
+ expect(Sidekiq.logger).to receive(:info).with(message: described_class::DEFAULT_VALUE_MESSAGE,
+ keys: [described_class.key_for('456'), described_class.key_for('012')])
+
+ expect(described_class.job_status(%w(123 456 789 012))).to eq([true, true, false, true])
+ end
+ end
+
+ context 'when log_implicit_sidekiq_status_calls is disabled' do
+ before do
+ stub_feature_flags(log_implicit_sidekiq_status_calls: false)
+ end
+
+ it 'does not perform any logging' do
+ described_class.set('123', value: 2)
+ described_class.set('456')
+
+ expect(Sidekiq.logger).not_to receive(:info)
+
+ expect(described_class.job_status(%w(123 456 789))).to eq([true, true, false])
+ end
+ end
+ end
end
diff --git a/spec/tooling/danger/project_helper_spec.rb b/spec/tooling/danger/project_helper_spec.rb
index ec475df6d83..68d75d06af6 100644
--- a/spec/tooling/danger/project_helper_spec.rb
+++ b/spec/tooling/danger/project_helper_spec.rb
@@ -86,7 +86,7 @@ RSpec.describe Tooling::Danger::ProjectHelper do
'rubocop/foo' | [:backend]
'.rubocop.yml' | [:backend]
'.rubocop_todo.yml' | [:backend]
- '.rubocop_manual_todo.yml' | [:backend]
+ '.rubocop_todo/cop/name.yml' | [:backend]
'spec/foo' | [:backend]
'spec/foo/bar' | [:backend]
@@ -192,6 +192,7 @@ RSpec.describe Tooling::Danger::ProjectHelper do
'spec/frontend/tracking_spec.js' | [:frontend, :product_intelligence]
'lib/gitlab/usage_database/foo.rb' | [:backend]
'config/metrics/counts_7d/test_metric.yml' | [:product_intelligence]
+ 'config/events/snowplow_event.yml' | [:product_intelligence]
'config/metrics/schema.json' | [:product_intelligence]
'doc/api/usage_data.md' | [:product_intelligence]
'spec/lib/gitlab/usage_data_spec.rb' | [:product_intelligence]
diff --git a/tooling/danger/project_helper.rb b/tooling/danger/project_helper.rb
index 5d338393f90..7eaf6eb25ea 100644
--- a/tooling/danger/project_helper.rb
+++ b/tooling/danger/project_helper.rb
@@ -127,7 +127,7 @@ module Tooling
%r{\A((spec/)?lib/generators/gitlab/usage_metric_)} => [:product_intelligence],
%r{\A((ee|jh)/)?lib/gitlab/usage_data_counters/.*\.yml\z} => [:product_intelligence],
- %r{\A((ee|jh)/)?config/metrics/((.*\.yml)|(schema\.json))\z} => [:product_intelligence],
+ %r{\A((ee|jh)/)?config/(events|metrics)/((.*\.yml)|(schema\.json))\z} => [:product_intelligence],
%r{\A((ee|jh)/)?lib/gitlab/usage_data(_counters)?(/|\.rb)} => [:backend, :product_intelligence],
%r{\A(
lib/gitlab/tracking\.rb |
@@ -151,7 +151,8 @@ module Tooling
%r{\A((ee|jh)/)?vendor/} => :backend,
%r{\A(Gemfile|Gemfile.lock|Rakefile)\z} => :backend,
%r{\A[A-Z_]+_VERSION\z} => :backend,
- %r{\A\.rubocop((_manual)?_todo)?\.yml\z} => :backend,
+ %r{\A\.rubocop(_todo)?\.yml\z} => :backend,
+ %r{\A\.rubocop_todo/.*\.yml\z} => :backend,
%r{\Afile_hooks/} => :backend,
%r{\A((ee|jh)/)?qa/} => :qa,