diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index e70b4523ae7..24ba9a38de6 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-2.6.0
+2.7.0
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index 23e4edea40f..dae24338e45 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -1,11 +1,9 @@
-
+
-
+
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 4a68fbb20d0..02396a4ba1b 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -1,8 +1,7 @@
-
+
diff --git a/app/assets/javascripts/ide/components/ide_review.vue b/app/assets/javascripts/ide/components/ide_review.vue
index 95348711e1d..e36d0a5a5b1 100644
--- a/app/assets/javascripts/ide/components/ide_review.vue
+++ b/app/assets/javascripts/ide/components/ide_review.vue
@@ -10,7 +10,7 @@ export default {
EditorModeDropdown,
},
computed: {
- ...mapGetters(['currentMergeRequest', 'activeFile']),
+ ...mapGetters(['currentMergeRequest', 'activeFile', 'getUrlForPath']),
...mapState(['viewer', 'currentMergeRequestId']),
showLatestChangesText() {
return !this.currentMergeRequestId || this.viewer === viewerTypes.diff;
@@ -24,7 +24,7 @@ export default {
},
mounted() {
if (this.activeFile && this.activeFile.pending && !this.activeFile.deleted) {
- this.$router.push(`/project${this.activeFile.url}`, () => {
+ this.$router.push(this.getUrlForPath(this.activeFile.path), () => {
this.updateViewer('editor');
});
} else if (this.activeFile && this.activeFile.deleted) {
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 647f4d4be85..747d5044790 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -15,13 +15,13 @@ export default {
},
computed: {
...mapState(['currentBranchId']),
- ...mapGetters(['currentProject', 'currentTree', 'activeFile']),
+ ...mapGetters(['currentProject', 'currentTree', 'activeFile', 'getUrlForPath']),
},
mounted() {
if (!this.activeFile) return;
if (this.activeFile.pending && !this.activeFile.deleted) {
- this.$router.push(`/project${this.activeFile.url}`, () => {
+ this.$router.push(this.getUrlForPath(this.activeFile.path), () => {
this.updateViewer('editor');
});
} else if (this.activeFile.deleted) {
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index d22d430cb4a..28348544f1a 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -48,6 +48,7 @@ export default {
'renderWhitespaceInCode',
'editorTheme',
'entries',
+ 'currentProjectId',
]),
...mapGetters([
'currentMergeRequest',
@@ -379,7 +380,7 @@ export default {
:path="file.rawPath || file.path"
:file-path="file.path"
:file-size="file.size"
- :project-path="file.projectId"
+ :project-path="currentProjectId"
:commit-sha="currentBranchCommit"
:type="fileType"
/>
@@ -390,7 +391,7 @@ export default {
:new-sha="currentMergeRequest.sha"
:old-path="file.mrChange.old_path"
:old-sha="currentMergeRequest.baseCommitSha"
- :project-path="file.projectId"
+ :project-path="currentProjectId"
/>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index bc6d23adb64..60a80a31a8b 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -1,5 +1,5 @@
-
+
{{
selectedMilestonesLabel
@@ -246,5 +246,5 @@ export default {
{{ item.text }}
-
+
diff --git a/app/assets/javascripts/monitoring/components/alert_widget_form.vue b/app/assets/javascripts/monitoring/components/alert_widget_form.vue
index f5d29d63dfd..506a98f16f7 100644
--- a/app/assets/javascripts/monitoring/components/alert_widget_form.vue
+++ b/app/assets/javascripts/monitoring/components/alert_widget_form.vue
@@ -7,7 +7,7 @@ import {
GlButtonGroup,
GlFormGroup,
GlFormInput,
- GlNewDropdown as GlDropdown,
+ GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlModal,
GlTooltipDirective,
diff --git a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue
index 68afa2ace01..b7834758a2e 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue
@@ -2,7 +2,7 @@
import { mapState, mapGetters, mapActions } from 'vuex';
import {
GlDeprecatedButton,
- GlNewDropdown,
+ GlDropdown,
GlNewDropdownDivider,
GlNewDropdownItem,
GlModal,
@@ -23,7 +23,7 @@ import { getAddMetricTrackingOptions } from '../utils';
export default {
components: {
GlDeprecatedButton,
- GlNewDropdown,
+ GlDropdown,
GlNewDropdownDivider,
GlNewDropdownItem,
GlModal,
@@ -143,7 +143,7 @@ export default {
as part of https://gitlab.com/gitlab-org/gitlab-ui/-/issues/936
The variant will create a dropdown with an icon, no text and no caret
-->
-
-
+
diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue
index 67c56766d99..2c4aecba719 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_header.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue
@@ -3,7 +3,7 @@ import { debounce } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import {
GlButton,
- GlNewDropdown,
+ GlDropdown,
GlLoadingIcon,
GlNewDropdownItem,
GlNewDropdownHeader,
@@ -28,7 +28,7 @@ export default {
components: {
GlIcon,
GlButton,
- GlNewDropdown,
+ GlDropdown,
GlLoadingIcon,
GlNewDropdownItem,
GlNewDropdownHeader,
@@ -181,7 +181,7 @@ export default {
-
-
+
diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
index 278858d3a94..e8968241af9 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue
@@ -6,7 +6,7 @@ import {
GlIcon,
GlLink,
GlLoadingIcon,
- GlNewDropdown as GlDropdown,
+ GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlNewDropdownDivider as GlDropdownDivider,
GlModal,
diff --git a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
index a4d388fb064..c9f6e4bd2f5 100644
--- a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
+++ b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
@@ -2,7 +2,7 @@
import { mapState, mapGetters } from 'vuex';
import {
GlIcon,
- GlNewDropdown,
+ GlDropdown,
GlNewDropdownItem,
GlNewDropdownHeader,
GlNewDropdownDivider,
@@ -17,7 +17,7 @@ const events = {
export default {
components: {
GlIcon,
- GlNewDropdown,
+ GlDropdown,
GlNewDropdownItem,
GlNewDropdownHeader,
GlNewDropdownDivider,
@@ -73,7 +73,7 @@ export default {
};
-
-
+
diff --git a/app/assets/javascripts/monitoring/components/refresh_button.vue b/app/assets/javascripts/monitoring/components/refresh_button.vue
index 0e9605450ed..3414c28d7a6 100644
--- a/app/assets/javascripts/monitoring/components/refresh_button.vue
+++ b/app/assets/javascripts/monitoring/components/refresh_button.vue
@@ -4,7 +4,7 @@ import { mapActions } from 'vuex';
import {
GlButtonGroup,
GlButton,
- GlNewDropdown,
+ GlDropdown,
GlNewDropdownItem,
GlNewDropdownDivider,
GlTooltipDirective,
@@ -48,7 +48,7 @@ export default {
components: {
GlButtonGroup,
GlButton,
- GlNewDropdown,
+ GlDropdown,
GlNewDropdownItem,
GlNewDropdownDivider,
},
@@ -152,7 +152,7 @@ export default {
icon="retry"
@click="refresh"
/>
- {{ option.label }}
-
+
diff --git a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
index a7b7d597fb7..653aad3d2f5 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/delete_wiki_modal.vue
@@ -1,11 +1,12 @@
-
+
{{ selectedRef }}
@@ -208,5 +208,5 @@ export default {
-
+
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
index 9a60172db2e..878b331fb3c 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
@@ -59,7 +59,7 @@ export default {
};
},
assigneeUrl() {
- return this.user.web_url;
+ return this.user.web_url || this.user.webUrl;
},
},
};
diff --git a/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
new file mode 100644
index 00000000000..4697d85472b
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/issuable_assignees.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+ {{ __('None') }}
+
+
+
+
+
diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
index fed9e5886c0..95934c0ef2a 100644
--- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
@@ -73,9 +73,9 @@ export default {
:root-path="rootPath"
:issuable-type="issuableType"
>
-
-
{{ user.name }}
-
{{ username }}
+
+
{{ user.name }}
+
{{ username }}
diff --git a/app/assets/javascripts/users_select/index.js b/app/assets/javascripts/users_select/index.js
index ad86929ec0a..e73b226772f 100644
--- a/app/assets/javascripts/users_select/index.js
+++ b/app/assets/javascripts/users_select/index.js
@@ -55,6 +55,7 @@ function UsersSelect(currentUser, els, options = {}) {
const defaultLabel = $dropdown.data('defaultLabel');
const issueURL = $dropdown.data('issueUpdate');
const $selectbox = $dropdown.closest('.selectbox');
+ const $assignToMeLink = $selectbox.next('.assign-to-me-link');
let $block = $selectbox.closest('.block');
const abilityName = $dropdown.data('abilityName');
let $value = $block.find('.value');
@@ -161,7 +162,7 @@ function UsersSelect(currentUser, els, options = {}) {
});
};
- $('.assign-to-me-link').on('click', e => {
+ $assignToMeLink.on('click', e => {
e.preventDefault();
$(e.currentTarget).hide();
@@ -451,9 +452,9 @@ function UsersSelect(currentUser, els, options = {}) {
}
if (getSelected().find(u => u === gon.current_user_id)) {
- $('.assign-to-me-link').hide();
+ $assignToMeLink.hide();
} else {
- $('.assign-to-me-link').show();
+ $assignToMeLink.show();
}
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 3b449f4a76c..3a873a5a17e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -4,7 +4,7 @@ import Mousetrap from 'mousetrap';
import { escape } from 'lodash';
import {
GlButton,
- GlNewDropdown as GlDropdown,
+ GlDropdown,
GlNewDropdownHeader as GlDropdownHeader,
GlNewDropdownItem as GlDropdownItem,
GlTooltipDirective,
diff --git a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
index 6f5ea8dcbee..e02d55d372d 100644
--- a/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/clone_dropdown.vue
@@ -1,6 +1,6 @@
-
+
{{ $options.labels.ssh }}
@@ -85,5 +85,5 @@ export default {
-
+
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 004bf882dd6..c1c4f437dee 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -14,6 +14,11 @@ export default {
type: Object,
required: true,
},
+ fileUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
level: {
type: Number,
required: true,
@@ -48,6 +53,9 @@ export default {
// don't output a title if we don't have the expanded path
return this.file?.tree?.length ? this.file.tree[0].parentPath : false;
},
+ fileRouterUrl() {
+ return this.fileUrl || `/project${this.file.url}`;
+ },
},
watch: {
'file.active': function fileActiveWatch(active) {
@@ -74,7 +82,7 @@ export default {
this.toggleTreeOpen(this.file.path);
}
- if (this.$router) this.$router.push(`/project${this.file.url}`);
+ if (this.$router && !this.hasUrlAtCurrentRoute()) this.$router.push(this.fileRouterUrl);
if (this.isBlob) this.clickedFile(this.file.path);
},
@@ -104,7 +112,7 @@ export default {
hasUrlAtCurrentRoute() {
if (!this.$router || !this.$router.currentRoute) return true;
- return this.$router.currentRoute.path === `/project${escapeFileUrl(this.file.url)}`;
+ return this.$router.currentRoute.path === escapeFileUrl(this.fileRouterUrl);
},
},
};
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index dae7c921988..565bab9f8cc 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -3,7 +3,7 @@ import {
GlFilteredSearch,
GlButtonGroup,
GlButton,
- GlNewDropdown as GlDropdown,
+ GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlTooltipDirective,
} from '@gitlab/ui';
diff --git a/app/assets/javascripts/vue_shared/components/resizable_chart/constants.js b/app/assets/javascripts/vue_shared/components/resizable_chart/constants.js
index edc5ffb7b77..68d86777995 100644
--- a/app/assets/javascripts/vue_shared/components/resizable_chart/constants.js
+++ b/app/assets/javascripts/vue_shared/components/resizable_chart/constants.js
@@ -1,6 +1,6 @@
export const DEFAULT_RX = 0.4;
-export const DEFAULT_BAR_WIDTH = 6;
-export const DEFAULT_LABEL_WIDTH = 4;
-export const DEFAULT_LABEL_HEIGHT = 5;
+export const DEFAULT_BAR_WIDTH = 4;
+export const DEFAULT_LABEL_WIDTH = 3;
+export const DEFAULT_LABEL_HEIGHT = 3;
export const BAR_HEIGHTS = [5, 7, 9, 14, 21, 35, 50, 80];
export const GRID_YS = [30, 60, 90];
diff --git a/app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue b/app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue
index 306fa61780f..a9f35a73db0 100644
--- a/app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue
+++ b/app/assets/javascripts/vue_shared/components/resizable_chart/skeleton_loader.vue
@@ -61,35 +61,37 @@ export default {
};
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue b/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
index 148bd501a8e..135b9842cbf 100644
--- a/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
+++ b/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
@@ -1,12 +1,12 @@
-
+
{{ selectedTimezoneLabel }}
@@ -98,5 +98,5 @@ export default {
{{ $options.tranlations.noResultsText }}
-
+
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index c4852974a4d..7238ff6c77d 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -116,6 +116,7 @@
.board-title {
flex-direction: column;
+ height: 100%;
}
.board-title-caret {
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index bfb464f1ff2..9bf8781f0cd 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -361,13 +361,6 @@
margin: 0;
}
- .username {
- display: block;
- margin-top: 4px;
- font-size: 13px;
- font-weight: $gl-font-weight-normal;
- }
-
.hide-expanded {
display: none;
}
diff --git a/app/helpers/form_helper.rb b/app/helpers/form_helper.rb
index 67bfeb22d92..3dde5afcb92 100644
--- a/app/helpers/form_helper.rb
+++ b/app/helpers/form_helper.rb
@@ -55,6 +55,29 @@ module FormHelper
dropdown_data
end
+ def reviewers_dropdown_options(issuable_type)
+ {
+ toggle_class: 'js-reviewer-search js-multiselect js-save-user-data',
+ title: 'Request review from',
+ filter: true,
+ dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-reviewer',
+ placeholder: _('Search users'),
+ data: {
+ first_user: current_user&.username,
+ null_user: true,
+ current_user: true,
+ project_id: (@target_project || @project)&.id,
+ field_name: "#{issuable_type}[reviewer_ids][]",
+ default_label: 'Unassigned',
+ 'dropdown-header': 'Reviewer(s)',
+ multi_select: true,
+ 'input-meta': 'name',
+ 'always-show-selectbox': true,
+ current_user_info: UserSerializer.new.represent(current_user)
+ }
+ }
+ end
+
# Overwritten
def issue_supports_multiple_assignees?
false
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 2ba1d841c2e..8397c348790 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -181,7 +181,7 @@ module NotesHelper
reopenPath: reopen_issuable_path(issuable),
notesPath: notes_url,
prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES),
- lastFetchedAt: Time.now.to_i
+ lastFetchedAt: Time.now.to_i * ::Gitlab::UpdatedNotesPaginator::MICROSECOND
}
if issuable.is_a?(MergeRequest)
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 9a64fe98f86..784b242e2b5 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -6,15 +6,15 @@ module NotificationsHelper
def notification_icon_class(level)
case level.to_sym
when :disabled, :owner_disabled
- 'microphone-slash'
+ 'notifications-off'
when :participating
- 'volume-up'
+ 'notifications'
when :watch
'eye'
when :mention
'at'
when :global
- 'globe'
+ 'earth'
end
end
@@ -28,8 +28,8 @@ module NotificationsHelper
end
end
- def notification_icon(level, text = nil)
- icon("#{notification_icon_class(level)} fw", text: text)
+ def notification_icon(level)
+ sprite_icon("#{notification_icon_class(level)}")
end
def notification_title(level)
diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb
index 0318dedca11..36186157c5d 100644
--- a/app/models/alert_management/alert.rb
+++ b/app/models/alert_management/alert.rb
@@ -32,8 +32,6 @@ module AlertManagement
:acknowledged
].freeze
- DETAILS_IGNORED_PARAMS = %w(start_time).freeze
-
belongs_to :project
belongs_to :issue, optional: true
belongs_to :prometheus_alert, optional: true
@@ -119,7 +117,7 @@ module AlertManagement
end
delegate :iid, to: :issue, prefix: true, allow_nil: true
- delegate :metrics_dashboard_url, :details_url, to: :present
+ delegate :metrics_dashboard_url, :details_url, :details, to: :present
scope :for_iid, -> (iid) { where(iid: iid) }
scope :for_status, -> (status) { where(status: status) }
@@ -172,12 +170,6 @@ module AlertManagement
with_prometheus_alert.where(id: ids)
end
- def details
- details_payload = payload.except(*attributes.keys, *DETAILS_IGNORED_PARAMS)
-
- Gitlab::Utils::InlineHash.merge_keys(details_payload)
- end
-
def prometheus?
monitoring_tool == Gitlab::AlertManagement::AlertParams::MONITORING_TOOLS[:prometheus]
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 6e07872c473..b72bf55f879 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -146,6 +146,7 @@ class Project < ApplicationRecord
has_one :discord_service
has_one :drone_ci_service
has_one :emails_on_push_service
+ has_one :ewm_service
has_one :pipelines_email_service
has_one :irker_service
has_one :pivotaltracker_service
diff --git a/app/models/project_services/ewm_service.rb b/app/models/project_services/ewm_service.rb
new file mode 100644
index 00000000000..af402e50292
--- /dev/null
+++ b/app/models/project_services/ewm_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class EwmService < IssueTrackerService
+ validates :project_url, :issues_url, :new_issue_url, presence: true, public_url: true, if: :activated?
+
+ def self.reference_pattern(only_long: true)
+ @reference_pattern ||= %r{(?
\b(bug|task|work item|workitem|rtcwi|defect)\b\s+\d+)}i
+ end
+
+ def title
+ 'EWM'
+ end
+
+ def description
+ s_('IssueTracker|EWM work items tracker')
+ end
+
+ def self.to_param
+ 'ewm'
+ end
+
+ def can_test?
+ false
+ end
+
+ def issue_url(iid)
+ issues_url.gsub(':id', iid.to_s.split(' ')[-1])
+ end
+end
diff --git a/app/models/service.rb b/app/models/service.rb
index f2da6905674..91dc44b1ca0 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -13,7 +13,7 @@ class Service < ApplicationRecord
SERVICE_NAMES = %w[
alerts asana assembla bamboo bugzilla buildkite campfire confluence custom_issue_tracker discord
- drone_ci emails_on_push external_wiki flowdock hangouts_chat hipchat irker jira
+ drone_ci emails_on_push ewm external_wiki flowdock hangouts_chat hipchat irker jira
mattermost mattermost_slash_commands microsoft_teams packagist pipelines_email
pivotaltracker prometheus pushover redmine slack slack_slash_commands teamcity unify_circuit webex_teams youtrack
].freeze
diff --git a/app/presenters/alert_management/alert_presenter.rb b/app/presenters/alert_management/alert_presenter.rb
index 2daa3bd09bf..8017bc3e0cd 100644
--- a/app/presenters/alert_management/alert_presenter.rb
+++ b/app/presenters/alert_management/alert_presenter.rb
@@ -42,6 +42,10 @@ module AlertManagement
details_project_alert_management_url(project, alert.iid)
end
+ def details
+ Gitlab::Utils::InlineHash.merge_keys(payload)
+ end
+
private
attr_reader :alert, :project
@@ -81,7 +85,7 @@ module AlertManagement
end
def details_list
- alert.details
+ details
.map { |label, value| list_item(label, value) }
.join(MARKDOWN_LINE_BREAK)
end
diff --git a/app/presenters/alert_management/prometheus_alert_presenter.rb b/app/presenters/alert_management/prometheus_alert_presenter.rb
index 3bcc98e6784..0e939a65d42 100644
--- a/app/presenters/alert_management/prometheus_alert_presenter.rb
+++ b/app/presenters/alert_management/prometheus_alert_presenter.rb
@@ -12,10 +12,6 @@ module AlertManagement
alerting_alert.alert_markdown
end
- def details_list
- alerting_alert.annotation_list
- end
-
def metric_embed_for_alert
alerting_alert.metric_embed_for_alert
end
diff --git a/app/presenters/projects/prometheus/alert_presenter.rb b/app/presenters/projects/prometheus/alert_presenter.rb
index 694b9dff512..8ee7e2d2b34 100644
--- a/app/presenters/projects/prometheus/alert_presenter.rb
+++ b/app/presenters/projects/prometheus/alert_presenter.rb
@@ -3,7 +3,6 @@
module Projects
module Prometheus
class AlertPresenter < Gitlab::View::Presenter::Delegated
- RESERVED_ANNOTATIONS = %w(gitlab_incident_markdown gitlab_y_label title).freeze
GENERIC_ALERT_SUMMARY_ANNOTATIONS = %w(monitoring_tool service hosts).freeze
MARKDOWN_LINE_BREAK = " \n".freeze
INCIDENT_LABEL_NAME = ::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES[:title].freeze
@@ -56,11 +55,10 @@ module Projects
MARKDOWN
end
- def annotation_list
- strong_memoize(:annotation_list) do
- annotations
- .reject { |annotation| annotation.label.in?(RESERVED_ANNOTATIONS | GENERIC_ALERT_SUMMARY_ANNOTATIONS) }
- .map { |annotation| list_item(annotation.label, annotation.value) }
+ def details_list
+ strong_memoize(:details_list) do
+ details
+ .map { |label, value| list_item(label, value) }
.join(MARKDOWN_LINE_BREAK)
end
end
@@ -109,13 +107,17 @@ module Projects
metadata.join(MARKDOWN_LINE_BREAK)
end
+ def details
+ Gitlab::Utils::InlineHash.merge_keys(payload)
+ end
+
def alert_details
- if annotation_list.present?
+ if details.present?
<<~MARKDOWN.chomp
#### Alert Details
- #{annotation_list}
+ #{details_list}
MARKDOWN
end
end
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index 9a4668bb073..2252503d97e 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -44,6 +44,8 @@ module SystemNotes
def change_assignee(assignee)
body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}"
+ issue_activity_counter.track_issue_assignee_changed_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'assignee'))
end
@@ -74,6 +76,8 @@ module SystemNotes
body = text_parts.join(' and ')
+ issue_activity_counter.track_issue_assignee_changed_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'assignee'))
end
@@ -96,6 +100,8 @@ module SystemNotes
body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**"
+ issue_activity_counter.track_issue_title_changed_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
end
@@ -113,6 +119,8 @@ module SystemNotes
def change_description
body = 'changed the description'
+ issue_activity_counter.track_issue_description_changed_action(author: author) if noteable.is_a?(Issue)
+
create_note(NoteSummary.new(noteable, project, author, body, action: 'description'))
end
@@ -209,9 +217,13 @@ module SystemNotes
if noteable.confidential
body = 'made the issue confidential'
action = 'confidential'
+
+ issue_activity_counter.track_issue_made_confidential_action(author: author) if noteable.is_a?(Issue)
else
body = 'made the issue visible to everyone'
action = 'visible'
+
+ issue_activity_counter.track_issue_made_visible_action(author: author) if noteable.is_a?(Issue)
end
create_note(NoteSummary.new(noteable, project, author, body, action: action))
@@ -353,6 +365,10 @@ module SystemNotes
noteable.respond_to?(:resource_state_events) &&
::Feature.enabled?(:track_resource_state_change_events, noteable.project, default_enabled: true)
end
+
+ def issue_activity_counter
+ Gitlab::UsageDataCounters::IssueActivityUniqueCounter
+ end
end
end
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
index 404bb224655..ea698a296fb 100644
--- a/app/views/profiles/notifications/_group_settings.html.haml
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -2,7 +2,7 @@
.gl-responsive-table-row.notification-list-item
.table-section.section-40
- %span.notification.fa.fa-holder.gl-mr-2
+ %span.notification.gl-mr-2
= notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml
index f9172ae87aa..6e81d585f24 100644
--- a/app/views/profiles/notifications/_project_settings.html.haml
+++ b/app/views/profiles/notifications/_project_settings.html.haml
@@ -1,7 +1,7 @@
- emails_disabled = project.emails_disabled?
%li.notification-list-item
- %span.notification.fa.fa-holder.gl-mr-2
+ %span.notification.gl-mr-2
= notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml
index 9b1a467df6b..76ae63ca5e8 100644
--- a/app/views/shared/_no_password.html.haml
+++ b/app/views/shared/_no_password.html.haml
@@ -1,9 +1,12 @@
- if show_no_password_message?
- .no-password-message.alert.alert-warning
- - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password }
- - set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params
- = set_password_message.html_safe
- .alert-link-group
- = link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put
- |
- = link_to _('Remind later'), '#', class: 'hide-no-password-message'
+ .no-password-message.gl-alert.gl-alert-warning
+ = sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ %button.js-close.gl-alert-dismiss{ type: 'button', 'aria-label': _('Dismiss') }
+ = sprite_icon('close', size: 16, css_class: 'gl-icon')
+ .gl-alert-body
+ - translation_params = { protocol: gitlab_config.protocol.upcase, set_password_link: link_to_set_password }
+ - set_password_message = _("You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account") % translation_params
+ = set_password_message.html_safe
+ .gl-alert-actions
+ = link_to _('Remind later'), '#', class: 'hide-no-password-message btn gl-alert-action btn-info btn-md gl-button'
+ = link_to _("Don't show again"), profile_path(user: {hide_no_password: true}), method: :put, role: 'button', class: 'btn gl-alert-action btn-md btn-default gl-button btn-default-secondary'
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 86c73e36317..7a4c495e177 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -19,7 +19,7 @@
#board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
= render 'shared/issuable/search_bar', type: :boards, board: board
- - if Feature.enabled?(:boards_with_swimlanes, current_board_parent) || Feature.enabled?(:graphql_board_lists, current_board_parent)
+ - if Feature.enabled?(:boards_with_swimlanes, current_board_parent)
%board-content{ "v-cloak" => "true",
"ref" => "board_content",
":lists" => "state.lists",
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index b6088a77be7..459eb112e4f 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -12,6 +12,10 @@
.form-group.row.merge-request-assignee
= render "shared/issuable/form/metadata_issuable_assignee", issuable: issuable, form: form, has_due_date: has_due_date
+ - if issuable.allows_reviewers?
+ .form-group.row.merge-request-reviewer
+ = render "shared/issuable/form/metadata_issuable_reviewer", issuable: issuable, form: form, has_due_date: has_due_date
+
= render_if_exists "shared/issuable/form/epic", issuable: issuable, form: form, project: project
- if issuable.supports_milestone?
diff --git a/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml b/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml
new file mode 100644
index 00000000000..a8b033bba36
--- /dev/null
+++ b/app/views/shared/issuable/form/_metadata_issuable_reviewer.html.haml
@@ -0,0 +1,10 @@
+= form.label :reviewer_id, "Reviewer", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
+.col-sm-10{ class: ("col-md-8" if has_due_date) }
+ .issuable-form-select-holder.selectbox
+ - issuable.reviewers.each do |reviewer|
+ = hidden_field_tag "#{issuable.to_ability_name}[reviewer_ids][]", reviewer.id, id: nil, data: { meta: reviewer.name, avatar_url: reviewer.avatar_url, name: reviewer.name, username: reviewer.username }
+
+ - if issuable.reviewers.empty?
+ = hidden_field_tag "#{issuable.to_ability_name}[reviewer_ids][]", 0, id: nil, data: { meta: '' }
+
+ = dropdown_tag(users_dropdown_label(issuable.reviewers), options: reviewers_dropdown_options(issuable.to_ability_name))
diff --git a/changelogs/unreleased/209784-fix-notes-pagination-values.yml b/changelogs/unreleased/209784-fix-notes-pagination-values.yml
new file mode 100644
index 00000000000..6a19bceda94
--- /dev/null
+++ b/changelogs/unreleased/209784-fix-notes-pagination-values.yml
@@ -0,0 +1,5 @@
+---
+title: Use the correct start time when polling for updated notes
+merge_request: 42124
+author:
+type: fixed
diff --git a/changelogs/unreleased/224526-es-client-timeout.yml b/changelogs/unreleased/224526-es-client-timeout.yml
new file mode 100644
index 00000000000..017acfa991a
--- /dev/null
+++ b/changelogs/unreleased/224526-es-client-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Add admin setting of Elasticsearch client request timeout
+merge_request: 41470
+author:
+type: added
diff --git a/changelogs/unreleased/229918_systemnote_aggregate_counts.yml b/changelogs/unreleased/229918_systemnote_aggregate_counts.yml
new file mode 100644
index 00000000000..c62eec3e2c6
--- /dev/null
+++ b/changelogs/unreleased/229918_systemnote_aggregate_counts.yml
@@ -0,0 +1,5 @@
+---
+title: Add Issue actions to UsageData
+merge_request: 40904
+author:
+type: other
diff --git a/changelogs/unreleased/233665-replace-bootstrap-alerts-in-app-views-shared-_no_password-html-ham.yml b/changelogs/unreleased/233665-replace-bootstrap-alerts-in-app-views-shared-_no_password-html-ham.yml
new file mode 100644
index 00000000000..8bc251bf072
--- /dev/null
+++ b/changelogs/unreleased/233665-replace-bootstrap-alerts-in-app-views-shared-_no_password-html-ham.yml
@@ -0,0 +1,5 @@
+---
+title: Replace bootstrap alerts in app/views/shared/_no_password.html.haml
+merge_request: 41397
+author: Gilang Gumilar
+type: changed
diff --git a/changelogs/unreleased/ewm-proj-service.yml b/changelogs/unreleased/ewm-proj-service.yml
new file mode 100644
index 00000000000..9083ff987ae
--- /dev/null
+++ b/changelogs/unreleased/ewm-proj-service.yml
@@ -0,0 +1,5 @@
+---
+title: Added EWM work item tracker integration
+merge_request: 36662
+author:
+type: added
diff --git a/changelogs/unreleased/make-all-downstream-upstream-ids-links.yml b/changelogs/unreleased/make-all-downstream-upstream-ids-links.yml
new file mode 100644
index 00000000000..e2336609354
--- /dev/null
+++ b/changelogs/unreleased/make-all-downstream-upstream-ids-links.yml
@@ -0,0 +1,5 @@
+---
+title: Make Pipeline ID's always a link for downstream/upstream pipelines
+merge_request: 42107
+author:
+type: added
diff --git a/changelogs/unreleased/mf-remove-frontend-unit-test-report-case-sorting.yml b/changelogs/unreleased/mf-remove-frontend-unit-test-report-case-sorting.yml
new file mode 100644
index 00000000000..2f8fceceb9f
--- /dev/null
+++ b/changelogs/unreleased/mf-remove-frontend-unit-test-report-case-sorting.yml
@@ -0,0 +1,5 @@
+---
+title: Remove frontend unit test report test case sorting
+merge_request: 40885
+author:
+type: changed
diff --git a/changelogs/unreleased/mw-notification-icons.yml b/changelogs/unreleased/mw-notification-icons.yml
new file mode 100644
index 00000000000..227da4ba831
--- /dev/null
+++ b/changelogs/unreleased/mw-notification-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Replace notification icons with Gitlab SVGs
+merge_request: 40709
+author:
+type: changed
diff --git a/changelogs/unreleased/remove-duplicated-cs-findings.yml b/changelogs/unreleased/remove-duplicated-cs-findings.yml
new file mode 100644
index 00000000000..ac75e248473
--- /dev/null
+++ b/changelogs/unreleased/remove-duplicated-cs-findings.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicated container scanning findings
+merge_request: 42041
+author:
+type: other
diff --git a/changelogs/unreleased/sy-unhide-alert-payload-attrs.yml b/changelogs/unreleased/sy-unhide-alert-payload-attrs.yml
new file mode 100644
index 00000000000..3c5ee618543
--- /dev/null
+++ b/changelogs/unreleased/sy-unhide-alert-payload-attrs.yml
@@ -0,0 +1,5 @@
+---
+title: Present complete alert payload in detail and incident views
+merge_request: 42140
+author:
+type: changed
diff --git a/changelogs/unreleased/wc-pages-backup.yml b/changelogs/unreleased/wc-pages-backup.yml
new file mode 100644
index 00000000000..90689a30720
--- /dev/null
+++ b/changelogs/unreleased/wc-pages-backup.yml
@@ -0,0 +1,5 @@
+---
+title: Exclude tmp dirs from backups
+merge_request: 41706
+author:
+type: fixed
diff --git a/config/feature_flags/development/track_issue_activity_actions.yml b/config/feature_flags/development/track_issue_activity_actions.yml
new file mode 100644
index 00000000000..034b697ab52
--- /dev/null
+++ b/config/feature_flags/development/track_issue_activity_actions.yml
@@ -0,0 +1,7 @@
+---
+name: track_issue_activity_actions
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40904
+rollout_issue_url:
+group: group::project_management
+type: development
+default_enabled: false
\ No newline at end of file
diff --git a/db/migrate/20200903054946_add_elasticsearch_client_timeout.rb b/db/migrate/20200903054946_add_elasticsearch_client_timeout.rb
new file mode 100644
index 00000000000..93e70461641
--- /dev/null
+++ b/db/migrate/20200903054946_add_elasticsearch_client_timeout.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddElasticsearchClientTimeout < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :elasticsearch_client_request_timeout, :integer, null: false, default: 0
+ end
+end
diff --git a/db/post_migrate/20200910131217_tmp_index_for_fixing_inconsistent_vulnerability_occurrences.rb b/db/post_migrate/20200910131217_tmp_index_for_fixing_inconsistent_vulnerability_occurrences.rb
new file mode 100644
index 00000000000..1985b8f1b67
--- /dev/null
+++ b/db/post_migrate/20200910131217_tmp_index_for_fixing_inconsistent_vulnerability_occurrences.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class TmpIndexForFixingInconsistentVulnerabilityOccurrences < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'tmp_index_for_fixing_inconsistent_vulnerability_occurrences'
+ disable_ddl_transaction!
+
+ def up
+ # report_type: 2 container scanning
+ add_concurrent_index(:vulnerability_occurrences, :id,
+ where: "LENGTH(location_fingerprint) = 40 AND report_type = 2",
+ name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:vulnerability_occurrences, INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20200910131218_remove_duplicated_cs_findings.rb b/db/post_migrate/20200910131218_remove_duplicated_cs_findings.rb
new file mode 100644
index 00000000000..c5bfddfd265
--- /dev/null
+++ b/db/post_migrate/20200910131218_remove_duplicated_cs_findings.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class RemoveDuplicatedCsFindings < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ BATCH_SIZE = 1_000
+ INTERVAL = 2.minutes
+
+ # 23_893 records will be updated
+ # 23_893 records will be deleted
+ def up
+ return unless Gitlab.com?
+
+ migration = Gitlab::BackgroundMigration::RemoveDuplicateCsFindings
+ migration_name = migration.to_s.demodulize
+ relation = migration::Finding.container_scanning.where("LENGTH(location_fingerprint) = 40")
+ queue_background_migration_jobs_by_range_at_intervals(relation,
+ migration_name,
+ INTERVAL,
+ batch_size: BATCH_SIZE)
+ end
+
+ def down
+ # no-op
+ # intentionally blank
+ end
+end
diff --git a/db/schema_migrations/20200903054946 b/db/schema_migrations/20200903054946
new file mode 100644
index 00000000000..20a4874b1bc
--- /dev/null
+++ b/db/schema_migrations/20200903054946
@@ -0,0 +1 @@
+f9aa112661a55c9eeed1a6aa05dd4c28d6f88971dc14bb606e677d4b4a4e5947
\ No newline at end of file
diff --git a/db/schema_migrations/20200910131217 b/db/schema_migrations/20200910131217
new file mode 100644
index 00000000000..6aba73661f7
--- /dev/null
+++ b/db/schema_migrations/20200910131217
@@ -0,0 +1 @@
+205580c1ba38fd03ce025dfd1d9be67756ade4fd28ba957bb71cd9e5e89ef190
\ No newline at end of file
diff --git a/db/schema_migrations/20200910131218 b/db/schema_migrations/20200910131218
new file mode 100644
index 00000000000..c0e5f36d8d7
--- /dev/null
+++ b/db/schema_migrations/20200910131218
@@ -0,0 +1 @@
+ba431f19818b93da91c4ed2c3f25dc8e2f62c6d9ac07b15f6d01f21f085c1730
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index f14c4277756..afa480bc412 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -9271,6 +9271,7 @@ CREATE TABLE public.application_settings (
elasticsearch_indexed_file_size_limit_kb integer DEFAULT 1024 NOT NULL,
enforce_namespace_storage_limit boolean DEFAULT false NOT NULL,
container_registry_delete_tags_service_timeout integer DEFAULT 250 NOT NULL,
+ elasticsearch_client_request_timeout integer DEFAULT 0 NOT NULL,
CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)),
CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)),
CONSTRAINT check_d03919528d CHECK ((char_length(container_registry_vendor) <= 255)),
@@ -21400,6 +21401,8 @@ CREATE INDEX tmp_build_stage_position_index ON public.ci_builds USING btree (sta
CREATE INDEX tmp_index_for_email_unconfirmation_migration ON public.emails USING btree (id) WHERE (confirmed_at IS NOT NULL);
+CREATE INDEX tmp_index_for_fixing_inconsistent_vulnerability_occurrences ON public.vulnerability_occurrences USING btree (id) WHERE ((length(location_fingerprint) = 40) AND (report_type = 2));
+
CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON public.merge_request_metrics USING btree (merge_request_id);
CREATE UNIQUE INDEX users_security_dashboard_projects_unique_index ON public.users_security_dashboard_projects USING btree (project_id, user_id);
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index 058c96fb24a..d97d2cf25a6 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -118,7 +118,6 @@ The following are required to run Geo:
- [CentOS](https://www.centos.org) 7.4+
- [Ubuntu](https://ubuntu.com) 16.04+
- PostgreSQL 11+ with [Streaming Replication](https://wiki.postgresql.org/wiki/Streaming_Replication)
-- Git 2.9+
- All nodes must run the same GitLab version.
Additionally, check GitLab's [minimum requirements](../../../install/requirements.md),
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 8b9d5c142f4..c60da31f093 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -16054,6 +16054,7 @@ enum ServiceType {
DISCORD_SERVICE
DRONE_CI_SERVICE
EMAILS_ON_PUSH_SERVICE
+ EWM_SERVICE
EXTERNAL_WIKI_SERVICE
FLOWDOCK_SERVICE
GITHUB_SERVICE
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 6bc5bbe97a0..dd88ba59a04 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -46953,6 +46953,12 @@
"isDeprecated": false,
"deprecationReason": null
},
+ {
+ "name": "EWM_SERVICE",
+ "description": null,
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
{
"name": "EXTERNAL_WIKI_SERVICE",
"description": null,
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c4adaeee4e9..a961ec79607 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -3373,6 +3373,7 @@ State of a Sentry error
| `DISCORD_SERVICE` | |
| `DRONE_CI_SERVICE` | |
| `EMAILS_ON_PUSH_SERVICE` | |
+| `EWM_SERVICE` | |
| `EXTERNAL_WIKI_SERVICE` | |
| `FLOWDOCK_SERVICE` | |
| `GITHUB_SERVICE` | |
diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md
index 4760816f5d0..cd1c24b756f 100644
--- a/doc/api/project_level_variables.md
+++ b/doc/api/project_level_variables.md
@@ -154,11 +154,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: " "https://git
## The `filter` parameter
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34490) in GitLab 13.2.
-> - It's deployed behind a feature flag, disabled by default.
-> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39209) on GitLab 13.3.
-> - It's enabled on GitLab.com.
-> - It's recommended for production use.
-> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable).
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/227052) in GitLab 13.4.
This parameter is used for filtering by attributes, such as `environment_scope`.
@@ -167,21 +163,3 @@ Example usage:
```shell
curl --request DELETE --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/projects/1/variables/VARIABLE_1?filter[environment_scope]=production"
```
-
-### Enable or disable
-
-It is deployed behind a feature flag that is **enabled by default**.
-[GitLab administrators with access to the GitLab Rails console](../administration/feature_flags.md)
-can opt to disable it for your instance.
-
-To disable it:
-
-```ruby
-Feature.disable(:ci_variables_api_filter_environment_scope)
-```
-
-To enable it:
-
-```ruby
-Feature.enable(:ci_variables_api_filter_environment_scope)
-```
diff --git a/doc/development/telemetry/usage_ping.md b/doc/development/telemetry/usage_ping.md
index 17cc5cfe609..54a0537f9e6 100644
--- a/doc/development/telemetry/usage_ping.md
+++ b/doc/development/telemetry/usage_ping.md
@@ -243,6 +243,21 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd) and [PF
Keys:
- `name`: unique event name.
+
+ Name format `__name`.
+
+ Use one of the following prefixes for the event's name:
+
+ - `g_` for group, as an event which is tracked for group.
+ - `p_` for project, as an event which is tracked for project.
+ - `i_` for instance, as an event which is tracked for instance.
+ - `a_` for events encompassing all `g_`, `p_`, `i_`.
+ - `o_` for other.
+
+ Consider including in the event's name the Redis slot in order to be able to count totals for a specific category.
+
+ Example names: `i_compliance_credential_inventory`, `g_analytics_contribution`.
+
- `category`: event category. Used for getting total counts for events in a category, for easier
access to a group of events.
- `redis_slot`: optional Redis slot; default value: event name. Used if needed to calculate totals
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 9aacf7ce633..13548f5fa0b 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -12,7 +12,7 @@ as the hardware requirements that are needed to install and use GitLab.
### Supported Linux distributions
- Ubuntu (16.04/18.04/20.04)
-- Debian (8/9/10)
+- Debian (9/10)
- CentOS (6/7/8)
- openSUSE (Leap 15.1/Enterprise Server 12.2)
- Red Hat Enterprise Linux (please use the CentOS packages and instructions)
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 9d09e79a559..c5c21644d1c 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -15,6 +15,7 @@ GitLab can be integrated with the following external issue trackers:
- Jira
- Redmine
- Bugzilla
+- EWM
- YouTrack
## Authentication sources
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 0aa14367241..ba3a557f337 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -227,6 +227,7 @@ The following Elasticsearch settings are available:
| `Maximum field length` | See [the explanation in instance limits.](../administration/instance_limits.md#maximum-field-length). |
| `Maximum bulk request size (MiB)` | The Maximum Bulk Request size is used by GitLab's Golang-based indexer processes and indicates how much data it ought to collect (and store in memory) in a given indexing process before submitting the payload to Elasticsearch’s Bulk API. This setting should be used with the Bulk request concurrency setting (see below) and needs to accommodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
| `Bulk request concurrency` | The Bulk request concurrency indicates how many of GitLab's Golang-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to Elasticsearch’s Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting should be used together with the Maximum bulk request size setting (see above) and needs to accommodate the resource constraints of both the Elasticsearch host(s) and the host(s) running GitLab's Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
+| `Client request timeout` | Elasticsearch HTTP client request timeout value in seconds. `0` means using the system default timeout value, which depends on the libraries that GitLab application is built upon. |
### Limiting namespaces and projects
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 8b4ebc337de..cde0093f53e 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -1,7 +1,7 @@
# External issue tracker
GitLab has a great [issue tracker](../user/project/issues/index.md) but you can also use an external one
-such as Jira, Redmine, YouTrack, or Bugzilla. External issue trackers are configurable per GitLab project.
+such as Jira, Redmine, YouTrack, Bugzilla, or EWM. External issue trackers are configurable per GitLab project.
Once configured, you can reference external issues using the format `CODE-123`, where:
@@ -26,6 +26,7 @@ Visit the links below for details:
- [YouTrack](../user/project/integrations/youtrack.md)
- [Jira](../user/project/integrations/jira.md)
- [Bugzilla](../user/project/integrations/bugzilla.md)
+- [EWM](../user/project/integrations/ewm.md)
- [Custom Issue Tracker](../user/project/integrations/custom_issue_tracker.md)
### Service Template
diff --git a/doc/user/project/integrations/ewm.md b/doc/user/project/integrations/ewm.md
new file mode 100644
index 00000000000..be89323a246
--- /dev/null
+++ b/doc/user/project/integrations/ewm.md
@@ -0,0 +1,30 @@
+# IBM Engineering Workflow Management (EWM) Integration **(CORE)**
+
+This service allows you to navigate from GitLab to EWM work items mentioned in merge request descriptions and commit messages. Each work item reference is automatically converted to a link back to the work item.
+
+NOTE: **Note:**
+This IBM product was [formerly named Rational Team Concert](https://jazz.net/blog/index.php/2019/04/23/renaming-the-ibm-continuous-engineering-portfolio/)(RTC). This integration is also compatible with all versions of RTC and EWM.
+
+1. From a GitLab project, navigate to **Settings > Integrations**, and then click **EWM**.
+1. Enter the information listed below.
+
+ | Field | Description |
+ | ----- | ----------- |
+ | `project_url` | URL of the EWM project area to link to the GitLab project. To obtain your project area URL, navigate to the path `/ccm/web/projects` and copy the listed project's URL. For example, `https://example.com/ccm/web/Example%20Project` |
+ | `issues_url` | URL to the work item editor in the EWM project area. The format is `/resource/itemName/com.ibm.team.workitem.WorkItem/:id`. For example, `https://example.com/ccm/resource/itemName/com.ibm.team.workitem.WorkItem/:id` |
+ | `new_issue_url` | URL to create a new work item in the EWM project area. Append the following fragment to your project area URL: `#action=com.ibm.team.workitem.newWorkItem`. For example, `https://example.com/ccm/web/projects/JKE%20Banking#action=com.ibm.team.workitem.newWorkItem` |
+
+## Reference EWM work items in commit messages
+
+You can use any of the keywords supported by the EWM Git Integration Toolkit to refer to work items. Work items can be referenced using the format: ` `.
+
+You can use the following keywords:
+
+- `bug`
+- `task`
+- `defect`
+- `rtcwi`
+- `workitem`
+- `work item`
+
+For more details, see the EWM documentation page [Creating links from commit comments](https://www.ibm.com/support/knowledgecenter/SSYMRC_7.0.0/com.ibm.team.connector.cq.doc/topics/t_creating_links_through_comments.html), which recommends against using the additionally-supported keyword `#` because of incompatibility with GitLab.
diff --git a/doc/user/project/integrations/overview.md b/doc/user/project/integrations/overview.md
index 9499c76a1e2..7a1f757c138 100644
--- a/doc/user/project/integrations/overview.md
+++ b/doc/user/project/integrations/overview.md
@@ -59,6 +59,7 @@ Click on the service links to see further configuration instructions and details
| [Prometheus](prometheus.md) | Monitor the performance of your deployed apps | No |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop | No |
| [Redmine](redmine.md) | Redmine issue tracker | No |
+| [EWM](ewm.md) | EWM work item tracker | No |
| [Unify Circuit](unify_circuit.md) | Receive events notifications in Unify Circuit | No |
| [Webex Teams](webex_teams.md) | Receive events notifications in Webex Teams | No |
| [YouTrack](youtrack.md) | YouTrack issue tracker | No |
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 02977375d7e..060266a478f 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -221,4 +221,4 @@ Feature.disable(:save_issuable_health_status)
- [Export issues](csv_export.md)
- [Issues API](../../../api/issues.md)
- Configure an [external issue tracker](../../../integration/external-issue-tracker.md)
- such as Jira, Redmine, or Bugzilla.
+ such as Jira, Redmine, Bugzilla, or EWM.
diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md
index 229c2fb7241..82f026a6938 100644
--- a/doc/user/search/advanced_global_search.md
+++ b/doc/user/search/advanced_global_search.md
@@ -46,6 +46,9 @@ The Advanced Search can be useful in various scenarios.
If you are dealing with huge amount of data and want to keep GitLab's search
fast, the Advanced Search will help you achieve that.
+NOTE: **Note:**
+Between versions 12.10 and 13.4, Advanced Search response times have improved by 80%.
+
### Promote innersourcing
Your company may consist of many different developer teams each of which has
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index 94d773367a8..4bceda51900 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -633,6 +633,26 @@ module API
desc: 'The issues URL'
}
],
+ 'ewm' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New Issue URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'Issues URL'
+ }
+ ],
'youtrack' => [
{
required: true,
@@ -735,6 +755,7 @@ module API
::DiscordService,
::DroneCiService,
::EmailsOnPushService,
+ ::EwmService,
::ExternalWikiService,
::FlowdockService,
::HangoutsChatService,
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index cea0cb3a19c..0b3ec10f1b4 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -17,7 +17,6 @@ module API
def find_variable(params)
variables = ::Ci::VariablesFinder.new(user_project, params).execute.to_a
- return variables.first unless ::Gitlab::Ci::Features.variables_api_filter_environment_scope?
return variables.first unless variables.many? # rubocop: disable CodeReuse/ActiveRecord
conflict!("There are multiple variables with provided parameters. Please use 'filter[environment_scope]'")
diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb
index 33658ae225f..c2266f0bad6 100644
--- a/lib/backup/artifacts.rb
+++ b/lib/backup/artifacts.rb
@@ -9,7 +9,7 @@ module Backup
def initialize(progress)
@progress = progress
- super('artifacts', JobArtifactUploader.root)
+ super('artifacts', JobArtifactUploader.root, excludes: ['tmp'])
end
end
end
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index dae9056a47b..619a62fd6f6 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -7,14 +7,17 @@ module Backup
class Files
include Backup::Helper
- attr_reader :name, :app_files_dir, :backup_tarball, :files_parent_dir
+ DEFAULT_EXCLUDE = 'lost+found'
- def initialize(name, app_files_dir)
+ attr_reader :name, :app_files_dir, :backup_tarball, :excludes, :files_parent_dir
+
+ def initialize(name, app_files_dir, excludes: [])
@name = name
@app_files_dir = File.realpath(app_files_dir)
@files_parent_dir = File.realpath(File.join(@app_files_dir, '..'))
@backup_files_dir = File.join(Gitlab.config.backup.path, File.basename(@app_files_dir) )
@backup_tarball = File.join(Gitlab.config.backup.path, name + '.tar.gz')
+ @excludes = [DEFAULT_EXCLUDE].concat(excludes)
end
# Copy files from public/files to backup/files
@@ -23,7 +26,7 @@ module Backup
FileUtils.rm_f(backup_tarball)
if ENV['STRATEGY'] == 'copy'
- cmd = %W(rsync -a --exclude=lost+found #{app_files_dir} #{Gitlab.config.backup.path})
+ cmd = [%w(rsync -a), exclude_dirs(:rsync), %W(#{app_files_dir} #{Gitlab.config.backup.path})].flatten
output, status = Gitlab::Popen.popen(cmd)
unless status == 0
@@ -31,10 +34,12 @@ module Backup
raise Backup::Error, 'Backup failed'
end
- run_pipeline!([%W(#{tar} --exclude=lost+found -C #{@backup_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
+ tar_cmd = [tar, exclude_dirs(:tar), %W(-C #{@backup_files_dir} -cf - .)].flatten
+ run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
FileUtils.rm_rf(@backup_files_dir)
else
- run_pipeline!([%W(#{tar} --exclude=lost+found -C #{app_files_dir} -cf - .), gzip_cmd], out: [backup_tarball, 'w', 0600])
+ tar_cmd = [tar, exclude_dirs(:tar), %W(-C #{app_files_dir} -cf - .)].flatten
+ run_pipeline!([tar_cmd, gzip_cmd], out: [backup_tarball, 'w', 0600])
end
end
@@ -81,5 +86,17 @@ module Backup
error = err_r.read
raise Backup::Error, "Backup failed. #{error}" unless error =~ regex
end
+
+ def exclude_dirs(fmt)
+ excludes.map do |s|
+ if s == DEFAULT_EXCLUDE
+ '--exclude=' + s
+ elsif fmt == :rsync
+ '--exclude=/' + s
+ elsif fmt == :tar
+ '--exclude=./' + s
+ end
+ end
+ end
end
end
diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb
index a4be728df08..d7aab33d7cb 100644
--- a/lib/backup/pages.rb
+++ b/lib/backup/pages.rb
@@ -9,7 +9,7 @@ module Backup
def initialize(progress)
@progress = progress
- super('pages', Gitlab.config.pages.path)
+ super('pages', Gitlab.config.pages.path, excludes: [::Projects::UpdatePagesService::TMP_EXTRACT_PATH])
end
end
end
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 5a20b6ae0a6..b6a62bc3f29 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -9,7 +9,7 @@ module Backup
def initialize(progress)
@progress = progress
- super('uploads', File.join(Gitlab.config.uploads.storage_path, "uploads"))
+ super('uploads', File.join(Gitlab.config.uploads.storage_path, "uploads"), excludes: ['tmp'])
end
end
end
diff --git a/lib/gitlab/background_migration/remove_duplicate_cs_findings.rb b/lib/gitlab/background_migration/remove_duplicate_cs_findings.rb
new file mode 100644
index 00000000000..cc9b0329556
--- /dev/null
+++ b/lib/gitlab/background_migration/remove_duplicate_cs_findings.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+# rubocop:disable Style/Documentation
+
+module Gitlab
+ module BackgroundMigration
+ class RemoveDuplicateCsFindings
+ def perform(start_id, stop_id)
+ end
+ end
+ end
+end
+
+Gitlab::BackgroundMigration::RemoveDuplicateCsFindings.prepend_if_ee('EE::Gitlab::BackgroundMigration::RemoveDuplicateCsFindings')
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index c792b985381..ff311004596 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -31,11 +31,6 @@ module Gitlab
::Feature.enabled?(:ci_store_pipeline_messages, project, default_enabled: true)
end
- # Remove in https://gitlab.com/gitlab-org/gitlab/-/issues/227052
- def self.variables_api_filter_environment_scope?
- ::Feature.enabled?(:ci_variables_api_filter_environment_scope, default_enabled: true)
- end
-
def self.raise_job_rules_without_workflow_rules_warning?
::Feature.enabled?(:ci_raise_job_rules_without_workflow_rules_warning, default_enabled: true)
end
diff --git a/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
new file mode 100644
index 00000000000..fc1b5a59487
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/issue_activity_unique_counter.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module IssueActivityUniqueCounter
+ ISSUE_TITLE_CHANGED = 'g_project_management_issue_title_changed'
+ ISSUE_DESCRIPTION_CHANGED = 'g_project_management_issue_description_changed'
+ ISSUE_ASSIGNEE_CHANGED = 'g_project_management_issue_assignee_changed'
+ ISSUE_MADE_CONFIDENTIAL = 'g_project_management_issue_made_confidential'
+ ISSUE_MADE_VISIBLE = 'g_project_management_issue_made_visible'
+ ISSUE_CATEGORY = 'issues_edit'
+
+ class << self
+ def track_issue_title_changed_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_TITLE_CHANGED, author, time)
+ end
+
+ def track_issue_description_changed_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_DESCRIPTION_CHANGED, author, time)
+ end
+
+ def track_issue_assignee_changed_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_ASSIGNEE_CHANGED, author, time)
+ end
+
+ def track_issue_made_confidential_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_MADE_CONFIDENTIAL, author, time)
+ end
+
+ def track_issue_made_visible_action(author:, time: Time.zone.now)
+ track_unique_action(ISSUE_MADE_VISIBLE, author, time)
+ end
+
+ private
+
+ def track_unique_action(action, author, time)
+ return unless Feature.enabled?(:track_issue_activity_actions)
+ return unless author
+
+ Gitlab::UsageDataCounters::HLLRedisCounter.track_event(author.id, action, time)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/known_events.yml b/lib/gitlab/usage_data_counters/known_events.yml
index 598a8c523d6..a0f20405bb8 100644
--- a/lib/gitlab/usage_data_counters/known_events.yml
+++ b/lib/gitlab/usage_data_counters/known_events.yml
@@ -173,3 +173,24 @@
redis_slot: incident_management
category: incident_management
aggregation: weekly
+# Project Management group
+- name: g_project_management_issue_title_changed
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_description_changed
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_assignee_changed
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_made_confidential
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
+- name: g_project_management_issue_made_visible
+ category: issues_edit
+ redis_slot: project_management
+ aggregation: daily
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index de1d06a9e2b..f43e2466afc 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8,6 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: gitlab 1.0.0\n"
"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-11 14:23+1000\n"
+"PO-Revision-Date: 2020-09-11 14:23+1000\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"Language: \n"
@@ -140,6 +142,11 @@ msgstr[1] ""
msgid "%d commits"
msgstr ""
+msgid "%d completed issue"
+msgid_plural "%d completed issues"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d contribution"
msgid_plural "%d contributions"
msgstr[0] ""
@@ -240,6 +247,11 @@ msgid_plural "%d more comments"
msgstr[0] ""
msgstr[1] ""
+msgid "%d open issue"
+msgid_plural "%d open issues"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d personal project will be removed and cannot be restored."
msgid_plural "%d personal projects will be removed and cannot be restored."
msgstr[0] ""
@@ -333,6 +345,9 @@ msgstr ""
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
+msgid "%{completedCount} completed weight"
+msgstr ""
+
msgid "%{completedWeight} of %{totalWeight} weight completed"
msgstr ""
@@ -389,6 +404,9 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
+msgid "%{count} total weight"
+msgstr ""
+
msgid "%{dashboard_path} could not be found."
msgstr ""
@@ -806,9 +824,6 @@ msgstr ""
msgid "%{total} open issue weight"
msgstr ""
-msgid "%{total} open issues"
-msgstr ""
-
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr ""
@@ -5167,6 +5182,9 @@ msgstr ""
msgid "Client authentication key password"
msgstr ""
+msgid "Client request timeout"
+msgstr ""
+
msgid "Clients"
msgstr ""
@@ -9249,6 +9267,9 @@ msgstr ""
msgid "Elasticsearch AWS IAM credentials"
msgstr ""
+msgid "Elasticsearch HTTP client timeout value in seconds."
+msgstr ""
+
msgid "Elasticsearch indexing restrictions"
msgstr ""
@@ -9999,6 +10020,9 @@ msgstr ""
msgid "Error deleting project. Check logs for error details."
msgstr ""
+msgid "Error fetching burnup chart data"
+msgstr ""
+
msgid "Error fetching diverging counts for branches. Please try again."
msgstr ""
@@ -13887,6 +13911,9 @@ msgstr ""
msgid "IssueTracker|Custom issue tracker"
msgstr ""
+msgid "IssueTracker|EWM work items tracker"
+msgstr ""
+
msgid "IssueTracker|Redmine issue tracker"
msgstr ""
@@ -23045,6 +23072,9 @@ msgstr ""
msgid "Sets weight to %{weight}."
msgstr ""
+msgid "Setting this to 0 means using the system default timeout value."
+msgstr ""
+
msgid "Settings"
msgstr ""
@@ -26666,6 +26696,9 @@ msgstr ""
msgid "Trending"
msgstr ""
+msgid "Trials|Create a new group to start your GitLab Gold trial."
+msgstr ""
+
msgid "Trials|Go back to GitLab"
msgstr ""
@@ -26675,6 +26708,15 @@ msgstr ""
msgid "Trials|You can always resume this process by selecting your avatar and choosing 'Start a Gold trial'"
msgstr ""
+msgid "Trials|You can apply your trial to a new group or an existing group."
+msgstr ""
+
+msgid "Trials|You can apply your trial to a new group or your personal account."
+msgstr ""
+
+msgid "Trials|You can apply your trial to a new group, an existing group, or your personal account."
+msgstr ""
+
msgid "Trials|You won't get a free trial right now but you can always resume this process by clicking on your avatar and choosing 'Start a free trial'"
msgstr ""
@@ -28745,9 +28787,6 @@ msgstr ""
msgid "You can always edit this later"
msgstr ""
-msgid "You can apply your Trial to your Personal account or create a New Group."
-msgstr ""
-
msgid "You can create a new one or check them in your %{pat_link_start}personal access tokens%{pat_link_end} settings"
msgstr ""
diff --git a/rubocop/cop/migration/create_table_with_foreign_keys.rb b/rubocop/cop/migration/create_table_with_foreign_keys.rb
index 37096a9352f..01cab032049 100644
--- a/rubocop/cop/migration/create_table_with_foreign_keys.rb
+++ b/rubocop/cop/migration/create_table_with_foreign_keys.rb
@@ -35,7 +35,7 @@ module RuboCop
)
PATTERN
- def_node_search :argument_name, <<~PATTERN
+ def_node_matcher :argument_name?, <<~PATTERN
{(sym $...) (str $...)}
PATTERN
@@ -87,7 +87,11 @@ module RuboCop
end
def arguments_to_table_names(arguments)
- arguments.flat_map { |argument| argument_name(argument).to_a.flatten.first.to_s.pluralize.to_sym }
+ arguments.select { |argument| argument_name?(argument) }
+ .map(&:value)
+ .map(&:to_s)
+ .map(&:pluralize)
+ .map(&:to_sym)
end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 99caf24cbf1..e3411e4f925 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -392,6 +392,12 @@ FactoryBot.define do
end
end
+ factory :ewm_project, parent: :project do
+ has_external_issue_tracker { true }
+
+ ewm_service
+ end
+
factory :project_with_design, parent: :project do
after(:create) do |project|
issue = create(:issue, project: project)
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 7fbf6f16dc7..9056fd97f13 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -116,6 +116,12 @@ FactoryBot.define do
issue_tracker
end
+ factory :ewm_service do
+ project
+ active { true }
+ issue_tracker
+ end
+
trait :issue_tracker do
transient do
create_data { true }
diff --git a/spec/features/groups/board_sidebar_spec.rb b/spec/features/groups/board_sidebar_spec.rb
index 690d661ba2f..3bbeed10948 100644
--- a/spec/features/groups/board_sidebar_spec.rb
+++ b/spec/features/groups/board_sidebar_spec.rb
@@ -19,8 +19,6 @@ RSpec.describe 'Group Issue Boards', :js do
let(:card) { find('.board:nth-child(1)').first('.board-card') }
before do
- # stubbing until sidebar work is done: https://gitlab.com/gitlab-org/gitlab/-/issues/230711
- stub_feature_flags(graphql_board_lists: false)
sign_in(user)
visit group_board_path(group, board)
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index cc631cc6dd0..7321f2243b9 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -152,9 +152,7 @@ RSpec.describe "Issues > User edits issue", :js do
visit project_issue_path(project, issue2)
page.within '.assignee' do
- page.within '.value .author' do
- expect(page).to have_content user.name
- end
+ expect(page).to have_content user.name
click_link 'Edit'
click_link user.name
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
index 2c949ed84f4..397ca70f4a1 100644
--- a/spec/features/merge_request/user_edits_mr_spec.rb
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -20,4 +20,32 @@ RSpec.describe 'Merge request > User edits MR' do
include_context 'merge request edit context'
it_behaves_like 'an editable merge request'
end
+
+ context 'when merge_request_reviewers is turned on' do
+ before do
+ stub_feature_flags(merge_request_reviewers: true)
+ end
+
+ context 'non-fork merge request' do
+ include_context 'merge request edit context'
+ it_behaves_like 'an editable merge request with reviewers'
+ end
+
+ context 'for a forked project' do
+ let(:source_project) { fork_project(target_project, nil, repository: true) }
+
+ include_context 'merge request edit context'
+ it_behaves_like 'an editable merge request with reviewers'
+ end
+ end
+
+ context 'when merge_request_reviewers is turned off' do
+ before do
+ stub_feature_flags(merge_request_reviewers: false)
+ end
+
+ it 'does not render reviewers dropdown' do
+ expect(page).not_to have_selector('.js-reviewer-search')
+ end
+ end
end
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index c7389460a07..d7ae79a1543 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'User activates issue tracker', :js do
fill_in 'service_new_issue_url', with: url unless skip_new_issue_url
end
- shared_examples 'external issue tracker activation' do |tracker:, skip_new_issue_url: false|
+ shared_examples 'external issue tracker activation' do |tracker:, skip_new_issue_url: false, skip_test: false|
describe 'user sets and activates the Service' do
context 'when the connection test succeeds' do
before do
@@ -25,7 +25,11 @@ RSpec.describe 'User activates issue tracker', :js do
visit_project_integration(tracker)
fill_form(skip_new_issue_url: skip_new_issue_url)
- click_test_integration
+ if skip_test
+ click_button('Save changes')
+ else
+ click_test_integration
+ end
end
it 'activates the service' do
@@ -47,7 +51,11 @@ RSpec.describe 'User activates issue tracker', :js do
visit_project_integration(tracker)
fill_form(skip_new_issue_url: skip_new_issue_url)
- click_test_then_save_integration
+ if skip_test
+ click_button('Save changes')
+ else
+ click_test_then_save_integration
+ end
expect(page).to have_content("#{tracker} activated.")
expect(current_path).to eq(edit_project_service_path(project, tracker.parameterize(separator: '_')))
@@ -80,4 +88,5 @@ RSpec.describe 'User activates issue tracker', :js do
it_behaves_like 'external issue tracker activation', tracker: 'YouTrack', skip_new_issue_url: true
it_behaves_like 'external issue tracker activation', tracker: 'Bugzilla'
it_behaves_like 'external issue tracker activation', tracker: 'Custom Issue Tracker'
+ it_behaves_like 'external issue tracker activation', tracker: 'EWM', skip_test: true
end
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index a84970eb736..717ab5f3add 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -98,35 +98,12 @@ export const mockMilestone = {
due_date: '2019-12-31',
};
-const assignees = [
- {
- id: 'gid://gitlab/User/2',
- username: 'angelina.herman',
- name: 'Bernardina Bosco',
- avatar: 'https://www.gravatar.com/avatar/eb7b664b13a30ad9f9ba4b61d7075470?s=80&d=identicon',
- webUrl: 'http://127.0.0.1:3000/angelina.herman',
- },
-];
-
-const labels = [
- {
- id: 'gid://gitlab/GroupLabel/5',
- title: 'Cosync',
- color: '#34ebec',
- description: null,
- },
-];
-
export const rawIssue = {
- title: 'Issue 1',
- id: 'gid://gitlab/Issue/436',
- iid: 27,
- dueDate: null,
- timeEstimate: 0,
- weight: null,
+ title: 'Testing',
+ id: 'gid://gitlab/Issue/1',
+ iid: 1,
confidential: false,
- referencePath: 'gitlab-org/gitlab-test#27',
- path: '/gitlab-org/gitlab-test/-/issues/27',
+ referencePath: 'gitlab-org/gitlab-test#1',
labels: {
nodes: [
{
@@ -138,24 +115,23 @@ export const rawIssue = {
],
},
assignees: {
- nodes: assignees,
- },
- epic: {
- id: 'gid://gitlab/Epic/41',
+ nodes: [
+ {
+ id: 1,
+ name: 'name',
+ username: 'username',
+ avatar_url: 'http://avatar_url',
+ },
+ ],
},
};
export const mockIssue = {
- id: 'gid://gitlab/Issue/436',
- iid: 27,
- title: 'Issue 1',
- dueDate: null,
- timeEstimate: 0,
- weight: null,
+ title: 'Testing',
+ id: 1,
+ iid: 1,
confidential: false,
- referencePath: 'gitlab-org/gitlab-test#27',
- path: '/gitlab-org/gitlab-test/-/issues/27',
- assignees,
+ referencePath: 'gitlab-org/gitlab-test#1',
labels: [
{
id: 1,
@@ -164,64 +140,44 @@ export const mockIssue = {
description: 'testing',
},
],
- epic: {
- id: 'gid://gitlab/Epic/41',
- },
+ assignees: [
+ {
+ id: 1,
+ name: 'name',
+ username: 'username',
+ avatar_url: 'http://avatar_url',
+ },
+ ],
};
export const mockIssueWithModel = new ListIssue(mockIssue);
export const mockIssue2 = {
- id: 'gid://gitlab/Issue/437',
- iid: 28,
- title: 'Issue 2',
- dueDate: null,
- timeEstimate: 0,
- weight: null,
+ title: 'Planning',
+ id: 2,
+ iid: 2,
confidential: false,
referencePath: 'gitlab-org/gitlab-test#2',
- path: '/gitlab-org/gitlab-test/-/issues/28',
- assignees,
- labels,
- epic: {
- id: 'gid://gitlab/Epic/40',
- },
+ labels: [
+ {
+ id: 1,
+ title: 'plan',
+ color: 'blue',
+ description: 'planning',
+ },
+ ],
+ assignees: [
+ {
+ id: 1,
+ name: 'name',
+ username: 'username',
+ avatar_url: 'http://avatar_url',
+ },
+ ],
};
export const mockIssue2WithModel = new ListIssue(mockIssue2);
-export const mockIssue3 = {
- id: 'gid://gitlab/Issue/438',
- iid: 29,
- title: 'Issue 3',
- referencePath: '#29',
- dueDate: null,
- timeEstimate: 0,
- weight: null,
- confidential: false,
- path: '/gitlab-org/gitlab-test/-/issues/28',
- assignees,
- labels,
- epic: null,
-};
-
-export const mockIssue4 = {
- id: 'gid://gitlab/Issue/439',
- iid: 30,
- title: 'Issue 4',
- referencePath: '#30',
- dueDate: null,
- timeEstimate: 0,
- weight: null,
- confidential: false,
- path: '/gitlab-org/gitlab-test/-/issues/28',
- assignees,
- labels,
- epic: null,
-};
-
-export const mockIssues = [mockIssue, mockIssue2];
-
export const BoardsMockData = {
GET: {
'/test/-/boards/1/lists/300/issues?id=300&page=1': {
@@ -283,7 +239,6 @@ export const mockLists = [
label: null,
assignee: null,
milestone: null,
- loading: false,
},
{
id: 'gid://gitlab/List/2',
@@ -300,22 +255,9 @@ export const mockLists = [
},
assignee: null,
milestone: null,
- loading: false,
},
];
export const mockListsWithModel = mockLists.map(listMock =>
Vue.observable(new List({ ...listMock, doNotFetchIssues: true })),
);
-
-export const mockIssuesByListId = {
- 'gid://gitlab/List/1': [mockIssue.id, mockIssue3.id, mockIssue4.id],
- 'gid://gitlab/List/2': mockIssues.map(({ id }) => id),
-};
-
-export const issues = {
- [mockIssue.id]: mockIssue,
- [mockIssue2.id]: mockIssue2,
- [mockIssue3.id]: mockIssue3,
- [mockIssue4.id]: mockIssue4,
-};
diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js
index 8efad4d3ac7..4eb1a370a9f 100644
--- a/spec/frontend/boards/stores/actions_spec.js
+++ b/spec/frontend/boards/stores/actions_spec.js
@@ -3,6 +3,7 @@ import {
mockListsWithModel,
mockLists,
mockIssue,
+ mockIssue2,
mockIssueWithModel,
mockIssue2WithModel,
rawIssue,
@@ -133,7 +134,7 @@ describe('createList', () => {
{ backlog: true },
state,
[],
- [{ type: 'addList', payload: backlogList }],
+ [{ type: 'addList', payload: { ...backlogList, id: 1 } }],
done,
);
});
@@ -231,15 +232,19 @@ describe('deleteList', () => {
expectNotImplemented(actions.deleteList);
});
+describe('fetchIssuesForList', () => {
+ expectNotImplemented(actions.fetchIssuesForList);
+});
+
describe('moveIssue', () => {
const listIssues = {
- 'gid://gitlab/List/1': [436, 437],
+ 'gid://gitlab/List/1': [mockIssue.id, mockIssue2.id],
'gid://gitlab/List/2': [],
};
const issues = {
- '436': mockIssueWithModel,
- '437': mockIssue2WithModel,
+ '1': mockIssueWithModel,
+ '2': mockIssue2WithModel,
};
const state = {
@@ -264,7 +269,7 @@ describe('moveIssue', () => {
testAction(
actions.moveIssue,
{
- issueId: '436',
+ issueId: mockIssue.id,
issueIid: mockIssue.iid,
issuePath: mockIssue.referencePath,
fromListId: 'gid://gitlab/List/1',
@@ -303,7 +308,7 @@ describe('moveIssue', () => {
testAction(
actions.moveIssue,
{
- issueId: '436',
+ issueId: mockIssue.id,
issueIid: mockIssue.iid,
issuePath: mockIssue.referencePath,
fromListId: 'gid://gitlab/List/1',
diff --git a/spec/frontend/boards/stores/getters_spec.js b/spec/frontend/boards/stores/getters_spec.js
index 288143a0f21..267451845ba 100644
--- a/spec/frontend/boards/stores/getters_spec.js
+++ b/spec/frontend/boards/stores/getters_spec.js
@@ -1,6 +1,5 @@
import getters from '~/boards/stores/getters';
import { inactiveId } from '~/boards/constants';
-import { mockIssue, mockIssue2, mockIssues, mockIssuesByListId, issues } from '../mock_data';
describe('Boards - Getters', () => {
describe('getLabelToggleState', () => {
@@ -116,18 +115,4 @@ describe('Boards - Getters', () => {
expect(getters.getActiveIssue(state)).toEqual(expected);
});
});
-
- describe('getIssues', () => {
- const boardsState = {
- issuesByListId: mockIssuesByListId,
- issues,
- };
- it('returns issues for a given listId', () => {
- const getIssueById = issueId => [mockIssue, mockIssue2].find(({ id }) => id === issueId);
-
- expect(getters.getIssues(boardsState, { getIssueById })('gid://gitlab/List/2')).toEqual(
- mockIssues,
- );
- });
- });
});
diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js
index a13a99a507e..350eccfa82e 100644
--- a/spec/frontend/boards/stores/mutations_spec.js
+++ b/spec/frontend/boards/stores/mutations_spec.js
@@ -54,11 +54,11 @@ describe('Board Store Mutations', () => {
});
});
- describe('RECEIVE_BOARD_LISTS_SUCCESS', () => {
+ describe('RECEIVE_LISTS', () => {
it('Should set boardLists to state', () => {
const lists = [listObj, listObjDuplicate];
- mutations[types.RECEIVE_BOARD_LISTS_SUCCESS](state, lists);
+ mutations[types.RECEIVE_LISTS](state, lists);
expect(state.boardLists).toEqual(lists);
});
@@ -145,33 +145,6 @@ describe('Board Store Mutations', () => {
expectNotImplemented(mutations.RECEIVE_REMOVE_LIST_ERROR);
});
- describe('RECEIVE_ISSUES_FOR_LIST_SUCCESS', () => {
- it('updates issuesByListId and issues on state', () => {
- const listIssues = {
- 'gid://gitlab/List/1': [mockIssue.id],
- };
- const issues = {
- '1': mockIssue,
- };
-
- state = {
- ...state,
- isLoadingIssues: true,
- issuesByListId: {},
- issues: {},
- boardLists: mockListsWithModel,
- };
-
- mutations.RECEIVE_ISSUES_FOR_LIST_SUCCESS(state, {
- listIssues: { listData: listIssues, issues },
- listId: 'gid://gitlab/List/1',
- });
-
- expect(state.issuesByListId).toEqual(listIssues);
- expect(state.issues).toEqual(issues);
- });
- });
-
describe('REQUEST_ISSUES_FOR_ALL_LISTS', () => {
it('sets isLoadingIssues to true', () => {
expect(state.isLoadingIssues).toBe(false);
@@ -182,28 +155,10 @@ describe('Board Store Mutations', () => {
});
});
- describe('RECEIVE_ISSUES_FOR_LIST_FAILURE', () => {
- it('sets error message', () => {
- state = {
- ...state,
- boardLists: mockListsWithModel,
- error: undefined,
- };
-
- const listId = 'gid://gitlab/List/1';
-
- mutations.RECEIVE_ISSUES_FOR_LIST_FAILURE(state, listId);
-
- expect(state.error).toEqual(
- 'An error occurred while fetching the board issues. Please reload the page.',
- );
- });
- });
-
describe('RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS', () => {
it('sets isLoadingIssues to false and updates issuesByListId object', () => {
const listIssues = {
- 'gid://gitlab/List/1': [mockIssue.id],
+ '': [mockIssue.id],
};
const issues = {
'1': mockIssue,
@@ -332,7 +287,7 @@ describe('Board Store Mutations', () => {
describe('MOVE_ISSUE_SUCCESS', () => {
it('updates issue in issues state', () => {
const issues = {
- '436': { id: rawIssue.id },
+ '1': { id: rawIssue.id },
};
state = {
@@ -344,7 +299,7 @@ describe('Board Store Mutations', () => {
issue: rawIssue,
});
- expect(state.issues).toEqual({ '436': { ...mockIssueWithModel, id: 436 } });
+ expect(state.issues).toEqual({ '1': { ...mockIssueWithModel, id: 1 } });
});
});
diff --git a/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
index 644cd0b5f27..99cb864ce34 100644
--- a/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
+++ b/spec/frontend/deploy_freeze/components/timezone_dropdown_spec.js
@@ -1,6 +1,6 @@
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { GlDeprecatedDropdownItem, GlNewDropdown } from '@gitlab/ui';
+import { GlDeprecatedDropdownItem, GlDropdown } from '@gitlab/ui';
import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue';
import createStore from '~/deploy_freeze/store';
@@ -92,7 +92,7 @@ describe('Deploy freeze timezone dropdown', () => {
});
it('renders selected time zone as dropdown label', () => {
- expect(wrapper.find(GlNewDropdown).vm.text).toBe('Alaska');
+ expect(wrapper.find(GlDropdown).vm.text).toBe('Alaska');
});
});
});
diff --git a/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap b/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
index d6fd09eb698..335cbe9c55e 100644
--- a/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
+++ b/spec/frontend/design_management/components/upload/__snapshots__/design_version_dropdown_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Design management design version dropdown component renders design version dropdown button 1`] = `
-
-
+
`;
exports[`Design management design version dropdown component renders design version list 1`] = `
-
-
+
`;
diff --git a/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js b/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
index f4206cdaeb3..cc4274b0d02 100644
--- a/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
+++ b/spec/frontend/design_management/components/upload/design_version_dropdown_spec.js
@@ -1,5 +1,5 @@
import { shallowMount } from '@vue/test-utils';
-import { GlNewDropdown, GlNewDropdownItem, GlSprintf } from '@gitlab/ui';
+import { GlDropdown, GlNewDropdownItem, GlSprintf } from '@gitlab/ui';
import DesignVersionDropdown from '~/design_management/components/upload/design_version_dropdown.vue';
import mockAllVersions from './mock_data/all_versions';
@@ -75,7 +75,7 @@ describe('Design management design version dropdown component', () => {
createComponent();
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing latest version');
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
});
});
@@ -83,7 +83,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ maxVersions: 1 });
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing latest version');
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
});
});
@@ -91,7 +91,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ $route: designRouteFactory(PREVIOUS_VERSION_ID) });
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlNewDropdown).attributes('text')).toBe(`Showing version #1`);
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe(`Showing version #1`);
});
});
@@ -99,7 +99,7 @@ describe('Design management design version dropdown component', () => {
createComponent({ $route: designRouteFactory(LATEST_VERSION_ID) });
return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.find(GlNewDropdown).attributes('text')).toBe('Showing latest version');
+ expect(wrapper.find(GlDropdown).attributes('text')).toBe('Showing latest version');
});
});
diff --git a/spec/frontend/ide/commit_icon_spec.js b/spec/frontend/ide/commit_icon_spec.js
index e4a7394b089..0dfcae00298 100644
--- a/spec/frontend/ide/commit_icon_spec.js
+++ b/spec/frontend/ide/commit_icon_spec.js
@@ -7,7 +7,6 @@ const createFile = (name = 'name', id = name, type = '', parent = null) =>
id,
type,
icon: 'icon',
- url: 'url',
name,
path: parent ? `${parent.path}/${name}` : name,
parentPath: parent ? parent.path : '',
diff --git a/spec/frontend/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js
index 5a591d3dcd0..d5080acfe9f 100644
--- a/spec/frontend/ide/components/repo_tab_spec.js
+++ b/spec/frontend/ide/components/repo_tab_spec.js
@@ -34,7 +34,7 @@ describe('RepoTab', () => {
});
vm.$store.state.openFiles.push(vm.tab);
const close = vm.$el.querySelector('.multi-file-tab-close');
- const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`);
+ const name = vm.$el.querySelector(`[title]`);
expect(close.innerHTML).toContain('#close');
expect(name.textContent.trim()).toEqual(vm.tab.name);
diff --git a/spec/frontend/ide/components/repo_tabs_spec.js b/spec/frontend/ide/components/repo_tabs_spec.js
index df5b01770f5..b251f207853 100644
--- a/spec/frontend/ide/components/repo_tabs_spec.js
+++ b/spec/frontend/ide/components/repo_tabs_spec.js
@@ -1,27 +1,40 @@
-import Vue from 'vue';
-import repoTabs from '~/ide/components/repo_tabs.vue';
-import createComponent from '../../helpers/vue_mount_component_helper';
+import Vuex from 'vuex';
+import { mount, createLocalVue } from '@vue/test-utils';
+import { createStore } from '~/ide/stores';
+import RepoTabs from '~/ide/components/repo_tabs.vue';
import { file } from '../helpers';
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
describe('RepoTabs', () => {
- const openedFiles = [file('open1'), file('open2')];
- const RepoTabs = Vue.extend(repoTabs);
- let vm;
+ let wrapper;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ store.state.openFiles = [file('open1'), file('open2')];
+
+ wrapper = mount(RepoTabs, {
+ propsData: {
+ files: store.state.openFiles,
+ viewer: 'editor',
+ activeFile: file('activeFile'),
+ },
+ store,
+ localVue,
+ });
+ });
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
it('renders a list of tabs', done => {
- vm = createComponent(RepoTabs, {
- files: openedFiles,
- viewer: 'editor',
- activeFile: file('activeFile'),
- });
- openedFiles[0].active = true;
+ store.state.openFiles[0].active = true;
- vm.$nextTick(() => {
- const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
+ wrapper.vm.$nextTick(() => {
+ const tabs = [...wrapper.vm.$el.querySelectorAll('.multi-file-tab')];
expect(tabs.length).toEqual(2);
expect(tabs[0].parentNode.classList.contains('active')).toEqual(true);
diff --git a/spec/frontend/ide/helpers.js b/spec/frontend/ide/helpers.js
index 8caa9c2b437..0e85b523cbd 100644
--- a/spec/frontend/ide/helpers.js
+++ b/spec/frontend/ide/helpers.js
@@ -6,7 +6,6 @@ export const file = (name = 'name', id = name, type = '', parent = null) =>
id,
type,
icon: 'icon',
- url: 'url',
name,
path: parent ? `${parent.path}/${name}` : name,
parentPath: parent ? parent.path : '',
diff --git a/spec/frontend/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js
index 6974cdc4074..b223cf64278 100644
--- a/spec/frontend/ide/lib/files_spec.js
+++ b/spec/frontend/ide/lib/files_spec.js
@@ -2,25 +2,16 @@ import { viewerInformationForPath } from '~/vue_shared/components/content_viewer
import { decorateFiles, splitParent } from '~/ide/lib/files';
import { decorateData } from '~/ide/stores/utils';
-const TEST_BRANCH_ID = 'lorem-ipsum';
-const TEST_PROJECT_ID = 10;
-
const createEntries = paths => {
const createEntry = (acc, { path, type, children }) => {
- // Sometimes we need to end the url with a '/'
- const createUrl = base => (type === 'tree' ? `${base}/` : base);
-
const { name, parent } = splitParent(path);
const previewMode = viewerInformationForPath(name);
acc[path] = {
...decorateData({
- projectId: TEST_PROJECT_ID,
- branchId: TEST_BRANCH_ID,
id: path,
name,
path,
- url: createUrl(`/${TEST_PROJECT_ID}/${type}/${TEST_BRANCH_ID}/-/${path}`),
type,
previewMode,
binary: (previewMode && previewMode.binary) || false,
@@ -56,11 +47,7 @@ describe('IDE lib decorate files', () => {
{ path: 'README.md', type: 'blob', children: [] },
]);
- const { entries, treeList } = decorateFiles({
- data,
- branchId: TEST_BRANCH_ID,
- projectId: TEST_PROJECT_ID,
- });
+ const { entries, treeList } = decorateFiles({ data });
// Here we test the keys and then each key/value individually because `expect(entries).toEqual(expectedEntries)`
// was taking a very long time for some reason. Probably due to large objects and nested `expect.objectContaining`.
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index bc3f86702cf..d2c32a81811 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -146,7 +146,7 @@ describe('IDE services', () => {
it('gives back file.baseRaw for files with that property present', () => {
file.baseRaw = TEST_FILE_CONTENTS;
- return services.getBaseRawFileData(file, TEST_COMMIT_SHA).then(content => {
+ return services.getBaseRawFileData(file, TEST_PROJECT_ID, TEST_COMMIT_SHA).then(content => {
expect(content).toEqual(TEST_FILE_CONTENTS);
});
});
@@ -155,7 +155,7 @@ describe('IDE services', () => {
file.tempFile = true;
file.baseRaw = TEST_FILE_CONTENTS;
- return services.getBaseRawFileData(file, TEST_COMMIT_SHA).then(content => {
+ return services.getBaseRawFileData(file, TEST_PROJECT_ID, TEST_COMMIT_SHA).then(content => {
expect(content).toEqual(TEST_FILE_CONTENTS);
});
});
@@ -192,7 +192,7 @@ describe('IDE services', () => {
});
it('fetches file content', () =>
- services.getBaseRawFileData(file, TEST_COMMIT_SHA).then(content => {
+ services.getBaseRawFileData(file, TEST_PROJECT_ID, TEST_COMMIT_SHA).then(content => {
expect(content).toEqual(TEST_FILE_CONTENTS);
}));
},
diff --git a/spec/frontend/ide/stores/actions/file_spec.js b/spec/frontend/ide/stores/actions/file_spec.js
index 88e7a9fff36..5166e07abcb 100644
--- a/spec/frontend/ide/stores/actions/file_spec.js
+++ b/spec/frontend/ide/stores/actions/file_spec.js
@@ -27,6 +27,10 @@ describe('IDE store file actions', () => {
};
store = createStore();
+
+ store.state.currentProjectId = 'test/test';
+ store.state.currentBranchId = 'master';
+
router = createRouter(store);
jest.spyOn(store, 'commit');
@@ -72,10 +76,7 @@ describe('IDE store file actions', () => {
});
it('closes file & opens next available file', () => {
- const f = {
- ...file('newOpenFile'),
- url: '/newOpenFile',
- };
+ const f = file('newOpenFile');
store.state.openFiles.push(f);
store.state.entries[f.path] = f;
@@ -84,7 +85,7 @@ describe('IDE store file actions', () => {
.dispatch('closeFile', localFile)
.then(Vue.nextTick)
.then(() => {
- expect(router.push).toHaveBeenCalledWith(`/project${f.url}`);
+ expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/-/newOpenFile/');
});
});
@@ -296,7 +297,6 @@ describe('IDE store file actions', () => {
describe('Re-named success', () => {
beforeEach(() => {
localFile = file(`newCreate-${Math.random()}`);
- localFile.url = `project/getFileDataURL`;
localFile.prevPath = 'old-dull-file';
localFile.path = 'new-shiny-file';
store.state.entries[localFile.path] = localFile;
@@ -393,7 +393,11 @@ describe('IDE store file actions', () => {
tmpFile.mrChange = { new_file: false };
return store.dispatch('getRawFileData', { path: tmpFile.path }).then(() => {
- expect(service.getBaseRawFileData).toHaveBeenCalledWith(tmpFile, 'SHA');
+ expect(service.getBaseRawFileData).toHaveBeenCalledWith(
+ tmpFile,
+ 'gitlab-org/gitlab-ce',
+ 'SHA',
+ );
expect(tmpFile.baseRaw).toBe('baseraw');
});
});
@@ -660,7 +664,7 @@ describe('IDE store file actions', () => {
});
it('pushes route for active file', () => {
- expect(router.push).toHaveBeenCalledWith(`/project${tmpFile.url}`);
+ expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/-/tempFile/');
});
});
});
@@ -735,10 +739,8 @@ describe('IDE store file actions', () => {
});
it('pushes router URL when added', () => {
- store.state.currentBranchId = 'master';
-
return store.dispatch('openPendingTab', { file: f, keyPrefix: 'pending' }).then(() => {
- expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/');
+ expect(router.push).toHaveBeenCalledWith('/project/test/test/tree/master/');
});
});
});
diff --git a/spec/frontend/ide/stores/actions/merge_request_spec.js b/spec/frontend/ide/stores/actions/merge_request_spec.js
index 62971b9cad6..b1cceda9d85 100644
--- a/spec/frontend/ide/stores/actions/merge_request_spec.js
+++ b/spec/frontend/ide/stores/actions/merge_request_spec.js
@@ -453,11 +453,9 @@ describe('IDE store merge request actions', () => {
it('updates activity bar view and gets file data, if changes are found', done => {
store.state.entries.foo = {
- url: 'test',
type: 'blob',
};
store.state.entries.bar = {
- url: 'test',
type: 'blob',
};
diff --git a/spec/frontend/ide/stores/actions_spec.js b/spec/frontend/ide/stores/actions_spec.js
index f77dbd80025..ebf39df2f6f 100644
--- a/spec/frontend/ide/stores/actions_spec.js
+++ b/spec/frontend/ide/stores/actions_spec.js
@@ -123,7 +123,6 @@ describe('Multi-file store actions', () => {
it('creates temp tree', done => {
store
.dispatch('createTempEntry', {
- branchId: store.state.currentBranchId,
name: 'test',
type: 'tree',
})
@@ -150,7 +149,6 @@ describe('Multi-file store actions', () => {
store
.dispatch('createTempEntry', {
- branchId: store.state.currentBranchId,
name: 'testing/test',
type: 'tree',
})
@@ -176,7 +174,6 @@ describe('Multi-file store actions', () => {
store
.dispatch('createTempEntry', {
- branchId: store.state.currentBranchId,
name: 'testing',
type: 'tree',
})
@@ -197,7 +194,6 @@ describe('Multi-file store actions', () => {
store
.dispatch('createTempEntry', {
name,
- branchId: 'mybranch',
type: 'blob',
})
.then(() => {
@@ -217,7 +213,6 @@ describe('Multi-file store actions', () => {
store
.dispatch('createTempEntry', {
name,
- branchId: 'mybranch',
type: 'blob',
})
.then(() => {
@@ -237,7 +232,6 @@ describe('Multi-file store actions', () => {
store
.dispatch('createTempEntry', {
name,
- branchId: 'mybranch',
type: 'blob',
})
.then(() => {
@@ -249,7 +243,7 @@ describe('Multi-file store actions', () => {
});
it('sets tmp file as active', () => {
- createTempEntry(store, { name: 'test', branchId: 'mybranch', type: 'blob' });
+ createTempEntry(store, { name: 'test', type: 'blob' });
expect(store.dispatch).toHaveBeenCalledWith('setFileActive', 'test');
});
@@ -262,7 +256,6 @@ describe('Multi-file store actions', () => {
store
.dispatch('createTempEntry', {
name: 'test',
- branchId: 'mybranch',
type: 'blob',
})
.then(() => {
@@ -780,9 +773,11 @@ describe('Multi-file store actions', () => {
});
it('routes to the renamed file if the original file has been opened', done => {
+ store.state.currentProjectId = 'test/test';
+ store.state.currentBranchId = 'master';
+
Object.assign(store.state.entries.orig, {
opened: true,
- url: '/foo-bar.md',
});
store
@@ -792,7 +787,7 @@ describe('Multi-file store actions', () => {
})
.then(() => {
expect(router.push.mock.calls).toHaveLength(1);
- expect(router.push).toHaveBeenCalledWith(`/project/foo-bar.md`);
+ expect(router.push).toHaveBeenCalledWith(`/project/test/test/tree/master/-/renamed/`);
})
.then(done)
.catch(done.fail);
diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js
index dcf05329ce0..17fc08144f6 100644
--- a/spec/frontend/ide/stores/getters_spec.js
+++ b/spec/frontend/ide/stores/getters_spec.js
@@ -482,4 +482,15 @@ describe('IDE store getters', () => {
expect(localStore.getters.getAvailableFileName('foo-bar1.jpg')).toBe('foo-bar1.jpg');
});
});
+
+ describe('getUrlForPath', () => {
+ it('returns a route url for the given path', () => {
+ localState.currentProjectId = 'test/test';
+ localState.currentBranchId = 'master';
+
+ expect(localStore.getters.getUrlForPath('path/to/foo/bar-1.jpg')).toBe(
+ `/project/test/test/tree/master/-/path/to/foo/bar-1.jpg/`,
+ );
+ });
+ });
});
diff --git a/spec/frontend/ide/stores/integration_spec.js b/spec/frontend/ide/stores/integration_spec.js
index f95f036f572..b6a7c7fd02d 100644
--- a/spec/frontend/ide/stores/integration_spec.js
+++ b/spec/frontend/ide/stores/integration_spec.js
@@ -36,8 +36,6 @@ describe('IDE store integration', () => {
beforeEach(() => {
const { entries, treeList } = decorateFiles({
data: [`${TEST_PATH_DIR}/`, TEST_PATH, 'README.md'],
- projectId: TEST_PROJECT_ID,
- branchId: TEST_BRANCH,
});
Object.assign(entries[TEST_PATH], {
diff --git a/spec/frontend/ide/stores/mutations_spec.js b/spec/frontend/ide/stores/mutations_spec.js
index 1b29648fb8b..09e9481e5d4 100644
--- a/spec/frontend/ide/stores/mutations_spec.js
+++ b/spec/frontend/ide/stores/mutations_spec.js
@@ -113,8 +113,6 @@ describe('Multi-file store mutations', () => {
},
treeList: [tmpFile],
},
- projectId: 'gitlab-ce',
- branchId: 'master',
});
expect(localState.trees['gitlab-ce/master'].tree.length).toEqual(1);
@@ -272,7 +270,6 @@ describe('Multi-file store mutations', () => {
prevId: undefined,
prevPath: undefined,
prevName: undefined,
- prevUrl: undefined,
prevKey: undefined,
}),
);
@@ -337,7 +334,6 @@ describe('Multi-file store mutations', () => {
};
Object.assign(localState.entries['root-folder/oldPath'], {
parentPath: 'root-folder',
- url: 'root-folder/oldPath-blob-root-folder/oldPath',
});
mutations.RENAME_ENTRY(localState, {
@@ -366,9 +362,6 @@ describe('Multi-file store mutations', () => {
});
it('renames entry, preserving old parameters', () => {
- Object.assign(localState.entries.oldPath, {
- url: `project/-/oldPath`,
- });
const oldPathData = localState.entries.oldPath;
mutations.RENAME_ENTRY(localState, {
@@ -382,12 +375,10 @@ describe('Multi-file store mutations', () => {
id: 'newPath',
path: 'newPath',
name: 'newPath',
- url: `project/-/newPath`,
key: expect.stringMatching('newPath'),
prevId: 'oldPath',
prevName: 'oldPath',
prevPath: 'oldPath',
- prevUrl: `project/-/oldPath`,
prevKey: oldPathData.key,
prevParentPath: oldPathData.parentPath,
});
@@ -409,7 +400,6 @@ describe('Multi-file store mutations', () => {
prevId: expect.anything(),
prevName: expect.anything(),
prevPath: expect.anything(),
- prevUrl: expect.anything(),
prevKey: expect.anything(),
prevParentPath: expect.anything(),
}),
@@ -419,7 +409,7 @@ describe('Multi-file store mutations', () => {
it('properly handles files with spaces in name', () => {
const path = 'my fancy path';
const newPath = 'new path';
- const oldEntry = { ...file(path, path, 'blob'), url: `project/-/${path}` };
+ const oldEntry = file(path, path, 'blob');
localState.entries[path] = oldEntry;
@@ -435,12 +425,10 @@ describe('Multi-file store mutations', () => {
id: newPath,
path: newPath,
name: newPath,
- url: `project/-/new path`,
key: expect.stringMatching(newPath),
prevId: path,
prevName: path,
prevPath: path,
- prevUrl: `project/-/my fancy path`,
prevKey: oldEntry.key,
prevParentPath: oldEntry.parentPath,
});
@@ -549,7 +537,7 @@ describe('Multi-file store mutations', () => {
it('correctly saves original values if an entry is renamed multiple times', () => {
const original = { ...localState.entries.oldPath };
- const paramsToCheck = ['prevId', 'prevPath', 'prevName', 'prevUrl'];
+ const paramsToCheck = ['prevId', 'prevPath', 'prevName'];
const expectedObj = paramsToCheck.reduce(
(o, param) => ({ ...o, [param]: original[param.replace('prev', '').toLowerCase()] }),
{},
@@ -577,7 +565,6 @@ describe('Multi-file store mutations', () => {
prevId: 'lorem/orig',
prevPath: 'lorem/orig',
prevName: 'orig',
- prevUrl: 'project/-/loren/orig',
prevKey: 'lorem/orig',
prevParentPath: 'lorem',
};
@@ -602,7 +589,6 @@ describe('Multi-file store mutations', () => {
prevId: undefined,
prevPath: undefined,
prevName: undefined,
- prevUrl: undefined,
prevKey: undefined,
prevParentPath: undefined,
}),
diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
index fd6af59eb48..a3a7d96bf14 100644
--- a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
+++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap
@@ -45,7 +45,7 @@ exports[`Alert integration settings form default state should match the default
-
-
+
{
});
const findGlLink = () => wrapper.find(GlLink);
- const findGlNewDropdown = () => wrapper.find(GlNewDropdown);
+ const findGlDropdown = () => wrapper.find(GlDropdown);
describe('template', () => {
describe('override prop is true', () => {
it('renders GlToggle as disabled', () => {
createComponent();
- expect(findGlNewDropdown().props('text')).toBe('Use custom settings');
+ expect(findGlDropdown().props('text')).toBe('Use custom settings');
});
});
@@ -49,7 +49,7 @@ describe('OverrideDropdown', () => {
it('renders GlToggle as disabled', () => {
createComponent({ override: false });
- expect(findGlNewDropdown().props('text')).toBe('Use default settings');
+ expect(findGlDropdown().props('text')).toBe('Use default settings');
});
});
diff --git a/spec/frontend/jira_import/components/jira_import_form_spec.js b/spec/frontend/jira_import/components/jira_import_form_spec.js
index 39cb63af2f0..d184c054b8a 100644
--- a/spec/frontend/jira_import/components/jira_import_form_spec.js
+++ b/spec/frontend/jira_import/components/jira_import_form_spec.js
@@ -1,4 +1,4 @@
-import { GlAlert, GlButton, GlNewDropdown, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui';
+import { GlAlert, GlButton, GlDropdown, GlFormSelect, GlLabel, GlTable } from '@gitlab/ui';
import { getByRole } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
@@ -35,7 +35,7 @@ describe('JiraImportForm', () => {
const getTable = () => wrapper.find(GlTable);
- const getUserDropdown = () => getTable().find(GlNewDropdown);
+ const getUserDropdown = () => getTable().find(GlDropdown);
const getHeader = name => getByRole(wrapper.element, 'columnheader', { name });
diff --git a/spec/frontend/milestones/project_milestone_combobox_spec.js b/spec/frontend/milestones/project_milestone_combobox_spec.js
index 4859561c4e3..60d68aa5816 100644
--- a/spec/frontend/milestones/project_milestone_combobox_spec.js
+++ b/spec/frontend/milestones/project_milestone_combobox_spec.js
@@ -1,7 +1,7 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
-import { GlNewDropdown, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
+import { GlDropdown, GlLoadingIcon, GlSearchBoxByType } from '@gitlab/ui';
import { ENTER_KEY } from '~/lib/utils/keys';
import MilestoneCombobox from '~/milestones/project_milestone_combobox.vue';
import { milestones as projectMilestones } from './mock_data';
@@ -53,7 +53,7 @@ describe('Milestone selector', () => {
});
it('renders the dropdown', () => {
- expect(wrapper.find(GlNewDropdown)).toExist();
+ expect(wrapper.find(GlDropdown)).toExist();
});
it('renders additional links', () => {
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index 80eacbe0a6a..a85c5a2bffc 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -32,7 +32,7 @@ exports[`Dashboard template matches the default snapshot 1`] = `
-
-
+
{
};
const findRefreshBtn = () => wrapper.find(GlButton);
- const findDropdown = () => wrapper.find(GlNewDropdown);
+ const findDropdown = () => wrapper.find(GlDropdown);
const findOptions = () => findDropdown().findAll(GlNewDropdownItem);
const findOptionAt = index => findOptions().at(index);
diff --git a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
index 406a2f8872c..4867e3de09e 100644
--- a/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
+++ b/spec/frontend/pipeline_new/components/pipeline_new_form_spec.js
@@ -1,5 +1,5 @@
import { mount, shallowMount } from '@vue/test-utils';
-import { GlNewDropdown, GlNewDropdownItem, GlForm } from '@gitlab/ui';
+import { GlDropdown, GlNewDropdownItem, GlForm } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
@@ -23,7 +23,7 @@ describe('Pipeline New Form', () => {
};
const findForm = () => wrapper.find(GlForm);
- const findDropdown = () => wrapper.find(GlNewDropdown);
+ const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownItems = () => wrapper.findAll(GlNewDropdownItem);
const findVariableRows = () => wrapper.findAll('[data-testid="ci-variable-row"]');
const findRemoveIcons = () => wrapper.findAll('[data-testid="remove-ci-variable-row"]');
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index d2afc1a1d55..8e65f0d4f71 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -17,7 +17,7 @@ describe('Linked pipeline', () => {
const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]');
const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' });
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
- const findPipelineLink = () => wrapper.find('[data-testid="childPipelineLink"]');
+ const findPipelineLink = () => wrapper.find('[data-testid="pipelineLink"]');
const findExpandButton = () => wrapper.find('[data-testid="expandPipelineButton"]');
const createWrapper = (propsData, data = []) => {
@@ -126,14 +126,14 @@ describe('Linked pipeline', () => {
expect(findPipelineLabel().exists()).toBe(true);
});
- it('downsteram pipeline should link to the child pipeline if child', () => {
+ it('downstream pipeline should contain the correct link', () => {
createWrapper(downstreamProps);
expect(findPipelineLink().attributes('href')).toBe(mockData.triggered_by.path);
});
- it('upstream pipeline should not contain a link', () => {
+ it('upstream pipeline should contain the correct link', () => {
createWrapper(upstreamProps);
- expect(findPipelineLink().exists()).toBe(false);
+ expect(findPipelineLink().attributes('href')).toBe(mockData.triggered_by.path);
});
it.each`
diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
index 3a4aa94571e..2feb6aa5799 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -23,8 +23,6 @@ describe('Test reports suite table', () => {
const noCasesMessage = () => wrapper.find('.js-no-test-cases');
const allCaseRows = () => wrapper.findAll('.js-case-row');
const findCaseRowAtIndex = index => wrapper.findAll('.js-case-row').at(index);
- const allCaseNames = () =>
- wrapper.findAll('[data-testid="caseName"]').wrappers.map(el => el.attributes('text'));
const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`);
const createComponent = (suite = testSuite) => {
@@ -63,16 +61,6 @@ describe('Test reports suite table', () => {
expect(allCaseRows().length).toBe(testCases.length);
});
- it('renders the failed tests first, skipped tests next, then successful tests', () => {
- const expectedCaseOrder = [
- ...testCases.filter(x => x.status === TestStatus.FAILED),
- ...testCases.filter(x => x.status === TestStatus.SKIPPED),
- ...testCases.filter(x => x.status === TestStatus.SUCCESS),
- ].map(x => x.name);
-
- expect(allCaseNames()).toEqual(expectedCaseOrder);
- });
-
it('renders the correct icon for each status', () => {
const failedTest = testCases.findIndex(x => x.status === TestStatus.FAILED);
const skippedTest = testCases.findIndex(x => x.status === TestStatus.SKIPPED);
diff --git a/spec/frontend/projects/commits/components/author_select_spec.js b/spec/frontend/projects/commits/components/author_select_spec.js
index a99c644a675..d38fdeb48a6 100644
--- a/spec/frontend/projects/commits/components/author_select_spec.js
+++ b/spec/frontend/projects/commits/components/author_select_spec.js
@@ -1,11 +1,6 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
-import {
- GlNewDropdown,
- GlNewDropdownHeader,
- GlSearchBoxByType,
- GlNewDropdownItem,
-} from '@gitlab/ui';
+import { GlDropdown, GlNewDropdownHeader, GlSearchBoxByType, GlNewDropdownItem } from '@gitlab/ui';
import * as urlUtility from '~/lib/utils/url_utility';
import AuthorSelect from '~/projects/commits/components/author_select.vue';
import { createStore } from '~/projects/commits/store';
@@ -63,7 +58,7 @@ describe('Author Select', () => {
});
const findDropdownContainer = () => wrapper.find({ ref: 'dropdownContainer' });
- const findDropdown = () => wrapper.find(GlNewDropdown);
+ const findDropdown = () => wrapper.find(GlDropdown);
const findDropdownHeader = () => wrapper.find(GlNewDropdownHeader);
const findSearchBox = () => wrapper.find(GlSearchBoxByType);
const findDropdownItems = () => wrapper.findAll(GlNewDropdownItem);
diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js
index 6490bdcf038..77671287a4c 100644
--- a/spec/frontend/ref/components/ref_selector_spec.js
+++ b/spec/frontend/ref/components/ref_selector_spec.js
@@ -177,7 +177,7 @@ describe('Ref selector component', () => {
return waitForRequests();
});
- it('adds the provided ID to the GlNewDropdown instance', () => {
+ it('adds the provided ID to the GlDropdown instance', () => {
expect(wrapper.attributes().id).toBe(id);
});
});
diff --git a/spec/frontend/sidebar/issuable_assignees_spec.js b/spec/frontend/sidebar/issuable_assignees_spec.js
new file mode 100644
index 00000000000..aa930bd4198
--- /dev/null
+++ b/spec/frontend/sidebar/issuable_assignees_spec.js
@@ -0,0 +1,59 @@
+import { shallowMount } from '@vue/test-utils';
+import IssuableAssignees from '~/sidebar/components/assignees/issuable_assignees.vue';
+import UncollapsedAssigneeList from '~/sidebar/components/assignees/uncollapsed_assignee_list.vue';
+
+describe('IssuableAssignees', () => {
+ let wrapper;
+
+ const createComponent = (props = { users: [] }) => {
+ wrapper = shallowMount(IssuableAssignees, {
+ provide: {
+ rootPath: '',
+ },
+ propsData: { ...props },
+ });
+ };
+ const findLabel = () => wrapper.find('[data-testid="assigneeLabel"');
+ const findUncollapsedAssigneeList = () => wrapper.find(UncollapsedAssigneeList);
+ const findEmptyAssignee = () => wrapper.find('[data-testid="none"]');
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ describe('when no assignees are present', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders "None"', () => {
+ expect(findEmptyAssignee().text()).toBe('None');
+ });
+
+ it('renders "0 assignees"', () => {
+ expect(findLabel().text()).toBe('0 Assignees');
+ });
+ });
+
+ describe('when assignees are present', () => {
+ it('renders UncollapsedAssigneesList', () => {
+ createComponent({ users: [{ id: 1 }] });
+
+ expect(findUncollapsedAssigneeList().exists()).toBe(true);
+ });
+
+ it.each`
+ assignees | expected
+ ${[{ id: 1 }]} | ${'Assignee'}
+ ${[{ id: 1 }, { id: 2 }]} | ${'2 Assignees'}
+ `(
+ 'when assignees have a length of $assignees.length, it renders $expected',
+ ({ assignees, expected }) => {
+ createComponent({ users: assignees });
+
+ expect(findLabel().text()).toBe(expected);
+ },
+ );
+ });
+});
diff --git a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
index e84eb7789d3..402a6daf922 100644
--- a/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
+++ b/spec/frontend/vue_shared/components/__snapshots__/clone_dropdown_spec.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
-
-
+
`;
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
index 2681488f76a..7b3c893ac05 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js
@@ -3,7 +3,7 @@ import {
GlFilteredSearch,
GlButtonGroup,
GlButton,
- GlNewDropdown as GlDropdown,
+ GlDropdown,
GlNewDropdownItem as GlDropdownItem,
} from '@gitlab/ui';
diff --git a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
index 70a60a2f746..3990248d021 100644
--- a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
+++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/skeleton_loader_spec.js.snap
@@ -1,425 +1,433 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Resizable Skeleton Loader default setup renders the bars, labels, and grid with correct position, size, and rx percentages 1`] = `
-
+
+
+
+
+
+
+
+
+
+
+
+
+
`;
exports[`Resizable Skeleton Loader with custom settings renders the correct position, and size percentages for bars and labels with different settings 1`] = `
-
+
+
+
+
+
+
+
+
+
+
+
+
+
`;
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 8d2806cbef6..176585e2ded 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -4,12 +4,12 @@ require 'spec_helper'
RSpec.describe NotificationsHelper do
describe 'notification_icon' do
- it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
- it { expect(notification_icon(:owner_disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
- it { expect(notification_icon(:participating)).to match('class="fa fa-volume-up fa-fw"') }
- it { expect(notification_icon(:mention)).to match('class="fa fa-at fa-fw"') }
- it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') }
- it { expect(notification_icon(:watch)).to match('class="fa fa-eye fa-fw"') }
+ it { expect(notification_icon(:disabled)).to match('data-testid="notifications-off-icon"') }
+ it { expect(notification_icon(:owner_disabled)).to match('data-testid="notifications-off-icon"') }
+ it { expect(notification_icon(:participating)).to match('data-testid="notifications-icon"') }
+ it { expect(notification_icon(:mention)).to match('data-testid="at-icon"') }
+ it { expect(notification_icon(:global)).to match('data-testid="earth-icon') }
+ it { expect(notification_icon(:watch)).to match('data-testid="eye-icon"') }
end
describe 'notification_title' do
diff --git a/spec/lib/backup/artifacts_spec.rb b/spec/lib/backup/artifacts_spec.rb
new file mode 100644
index 00000000000..2a3f1949ba5
--- /dev/null
+++ b/spec/lib/backup/artifacts_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Backup::Artifacts do
+ let(:progress) { StringIO.new }
+
+ subject(:backup) { described_class.new(progress) }
+
+ describe '#initialize' do
+ it 'uses the correct upload dir' do
+ Dir.mktmpdir do |tmpdir|
+ allow(JobArtifactUploader).to receive(:root) { "#{tmpdir}" }
+
+ expect(backup.app_files_dir).to eq("#{tmpdir}")
+ end
+ end
+ end
+
+ describe '#dump' do
+ before do
+ allow(File).to receive(:realpath).with('/var/gitlab-artifacts').and_return('/var/gitlab-artifacts')
+ allow(File).to receive(:realpath).with('/var/gitlab-artifacts/..').and_return('/var')
+ allow(JobArtifactUploader).to receive(:root) { '/var/gitlab-artifacts' }
+ end
+
+ it 'uses the correct artifact dir' do
+ expect(backup.app_files_dir).to eq('/var/gitlab-artifacts')
+ end
+
+ it 'excludes tmp from backup tar' do
+ expect(backup).to receive(:tar).and_return('blabla-tar')
+ expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/gitlab-artifacts -cf - .), 'gzip -c -1'], any_args)
+ backup.dump
+ end
+ end
+end
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index a7374b82ce0..c2dbaac7f15 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -14,6 +14,8 @@ RSpec.describe Backup::Files do
allow(File).to receive(:exist?).and_return(true)
allow(File).to receive(:realpath).with("/var/gitlab-registry").and_return("/var/gitlab-registry")
allow(File).to receive(:realpath).with("/var/gitlab-registry/..").and_return("/var")
+ allow(File).to receive(:realpath).with("/var/gitlab-pages").and_return("/var/gitlab-pages")
+ allow(File).to receive(:realpath).with("/var/gitlab-pages/..").and_return("/var")
allow_any_instance_of(String).to receive(:color) do |string, _color|
string
@@ -82,4 +84,48 @@ RSpec.describe Backup::Files do
end
end
end
+
+ describe '#dump' do
+ subject { described_class.new('pages', '/var/gitlab-pages', excludes: ['@pages.tmp']) }
+
+ before do
+ allow(subject).to receive(:run_pipeline!).and_return(true)
+ end
+
+ it 'raises no errors' do
+ expect { subject.dump }.not_to raise_error
+ end
+
+ it 'excludes tmp dirs from archive' do
+ expect(subject).to receive(:tar).and_return('blabla-tar')
+
+ expect(subject).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./@pages.tmp -C /var/gitlab-pages -cf - .), 'gzip -c -1'], any_args)
+ subject.dump
+ end
+
+ describe 'with STRATEGY=copy' do
+ before do
+ stub_env('STRATEGY', 'copy')
+ end
+
+ it 'excludes tmp dirs from rsync' do
+ allow(Gitlab.config.backup).to receive(:path) { '/var/gitlab-backup' }
+ allow(File).to receive(:realpath).with("/var/gitlab-backup").and_return("/var/gitlab-backup")
+
+ expect(Gitlab::Popen).to receive(:popen).with(%w(rsync -a --exclude=lost+found --exclude=/@pages.tmp /var/gitlab-pages /var/gitlab-backup)).and_return(['', 0])
+
+ subject.dump
+ end
+ end
+
+ describe '#exclude_dirs' do
+ it 'prepends a leading dot slash to tar excludes' do
+ expect(subject.exclude_dirs(:tar)).to eq(['--exclude=lost+found', '--exclude=./@pages.tmp'])
+ end
+
+ it 'prepends a leading slash to rsync excludes' do
+ expect(subject.exclude_dirs(:rsync)).to eq(['--exclude=lost+found', '--exclude=/@pages.tmp'])
+ end
+ end
+ end
end
diff --git a/spec/lib/backup/pages_spec.rb b/spec/lib/backup/pages_spec.rb
new file mode 100644
index 00000000000..59df4d1adf7
--- /dev/null
+++ b/spec/lib/backup/pages_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Backup::Pages do
+ let(:progress) { StringIO.new }
+
+ subject { described_class.new(progress) }
+
+ before do
+ allow(File).to receive(:realpath).with("/var/gitlab-pages").and_return("/var/gitlab-pages")
+ allow(File).to receive(:realpath).with("/var/gitlab-pages/..").and_return("/var")
+ end
+
+ describe '#dump' do
+ it 'uses the correct pages dir' do
+ allow(Gitlab.config.pages).to receive(:path) { '/var/gitlab-pages' }
+
+ expect(subject.app_files_dir).to eq('/var/gitlab-pages')
+ end
+
+ it 'excludes tmp from backup tar' do
+ allow(Gitlab.config.pages).to receive(:path) { '/var/gitlab-pages' }
+
+ expect(subject).to receive(:tar).and_return('blabla-tar')
+ expect(subject).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./@pages.tmp -C /var/gitlab-pages -cf - .), 'gzip -c -1'], any_args)
+ subject.dump
+ end
+ end
+end
diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb
index 7c2d715b580..678b670db34 100644
--- a/spec/lib/backup/uploads_spec.rb
+++ b/spec/lib/backup/uploads_spec.rb
@@ -18,4 +18,22 @@ RSpec.describe Backup::Uploads do
end
end
end
+
+ describe '#dump' do
+ before do
+ allow(File).to receive(:realpath).with('/var/uploads').and_return('/var/uploads')
+ allow(File).to receive(:realpath).with('/var/uploads/..').and_return('/var')
+ allow(Gitlab.config.uploads).to receive(:storage_path) { '/var' }
+ end
+
+ it 'uses the correct upload dir' do
+ expect(backup.app_files_dir).to eq('/var/uploads')
+ end
+
+ it 'excludes tmp from backup tar' do
+ expect(backup).to receive(:tar).and_return('blabla-tar')
+ expect(backup).to receive(:run_pipeline!).with([%w(blabla-tar --exclude=lost+found --exclude=./tmp -C /var/uploads -cf - .), 'gzip -c -1'], any_args)
+ backup.dump
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 7d8fb183dbb..e7b6c910b8a 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -208,4 +208,47 @@ RSpec.describe Banzai::Filter::ExternalIssueReferenceFilter do
end
end
end
+
+ context "ewm project" do
+ let_it_be(:project) { create(:ewm_project) }
+
+ before do
+ project.update!(issues_enabled: false)
+ end
+
+ context "rtcwi keyword" do
+ let(:issue) { ExternalIssue.new("rtcwi 123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "workitem keyword" do
+ let(:issue) { ExternalIssue.new("workitem 123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "defect keyword" do
+ let(:issue) { ExternalIssue.new("defect 123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "task keyword" do
+ let(:issue) { ExternalIssue.new("task 123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+
+ context "bug keyword" do
+ let(:issue) { ExternalIssue.new("bug 123", project) }
+ let(:reference) { issue.to_reference }
+
+ it_behaves_like "external issue tracker"
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index bfdc6c959d3..5f691799e9c 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -365,6 +365,7 @@ project:
- youtrack_service
- custom_issue_tracker_service
- bugzilla_service
+- ewm_service
- external_wiki_service
- mock_ci_service
- mock_deployment_service
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index c93fc2c17f1..f881da71251 100644
--- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.categories' do
it 'gets all unique category names' do
- expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code', 'incident_management')
+ expect(described_class.categories).to contain_exactly('analytics', 'compliance', 'ide_edit', 'search', 'source_code', 'incident_management', 'issues_edit')
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
new file mode 100644
index 00000000000..479fe36bcdd
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/issue_activity_unique_counter_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_gitlab_redis_shared_state do
+ let(:user1) { build(:user, id: 1) }
+ let(:user2) { build(:user, id: 2) }
+ let(:user3) { build(:user, id: 3) }
+ let(:time) { Time.zone.now }
+
+ shared_examples 'tracks and counts action' do
+ before do
+ stub_application_setting(usage_ping_enabled: true)
+ end
+
+ def count_unique(date_from:, date_to:)
+ Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
+ end
+
+ specify do
+ aggregate_failures do
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user1)).to be_truthy
+ expect(track_action(author: user2)).to be_truthy
+ expect(track_action(author: user3, time: time - 3.days)).to be_truthy
+
+ expect(count_unique(date_from: time, date_to: time)).to eq(2)
+ expect(count_unique(date_from: time - 5.days, date_to: 1.day.since(time))).to eq(3)
+ end
+ end
+
+ it 'does not track edit actions if author is not present' do
+ expect(track_action(author: nil)).to be_nil
+ end
+
+ context 'when feature flag track_issue_activity_actions is disabled' do
+ it 'does not track edit actions' do
+ stub_feature_flags(track_issue_activity_actions: false)
+
+ expect(track_action(author: user1)).to be_nil
+ end
+ end
+ end
+
+ context 'for Issue title edit actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_TITLE_CHANGED }
+
+ def track_action(params)
+ described_class.track_issue_title_changed_action(params)
+ end
+ end
+ end
+
+ context 'for Issue description edit actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_DESCRIPTION_CHANGED }
+
+ def track_action(params)
+ described_class.track_issue_description_changed_action(params)
+ end
+ end
+ end
+
+ context 'for Issue assignee edit actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_ASSIGNEE_CHANGED }
+
+ def track_action(params)
+ described_class.track_issue_assignee_changed_action(params)
+ end
+ end
+ end
+
+ context 'for Issue make confidential actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_MADE_CONFIDENTIAL }
+
+ def track_action(params)
+ described_class.track_issue_made_confidential_action(params)
+ end
+ end
+ end
+
+ context 'for Issue make visible actions' do
+ it_behaves_like 'tracks and counts action' do
+ let(:action) { described_class::ISSUE_MADE_VISIBLE }
+
+ def track_action(params)
+ described_class.track_issue_made_visible_action(params)
+ end
+ end
+ end
+
+ it 'can return the count of actions per user deduplicated', :aggregate_failures do
+ described_class.track_issue_title_changed_action(author: user1)
+ described_class.track_issue_description_changed_action(author: user1)
+ described_class.track_issue_assignee_changed_action(author: user1)
+ described_class.track_issue_title_changed_action(author: user2, time: time - 2.days)
+ described_class.track_issue_title_changed_action(author: user3, time: time - 3.days)
+ described_class.track_issue_description_changed_action(author: user3, time: time - 3.days)
+ described_class.track_issue_assignee_changed_action(author: user3, time: time - 3.days)
+
+ events = Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(described_class::ISSUE_CATEGORY)
+ today_count = Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: time, end_date: time)
+ week_count = Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: events, start_date: time - 5.days, end_date: 1.day.since(time))
+
+ expect(today_count).to eq(1)
+ expect(week_count).to eq(3)
+ end
+end
diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb
index f937a879400..87aeb3d78c0 100644
--- a/spec/models/alert_management/alert_spec.rb
+++ b/spec/models/alert_management/alert_spec.rb
@@ -332,33 +332,6 @@ RSpec.describe AlertManagement::Alert do
end
end
- describe '#details' do
- let(:payload) do
- {
- 'title' => 'Details title',
- 'custom' => {
- 'alert' => {
- 'fields' => %w[one two]
- }
- },
- 'yet' => {
- 'another' => 'field'
- }
- }
- end
-
- let(:alert) { build(:alert_management_alert, project: project, title: 'Details title', payload: payload) }
-
- subject { alert.details }
-
- it 'renders the payload as inline hash' do
- is_expected.to eq(
- 'custom.alert.fields' => %w[one two],
- 'yet.another' => 'field'
- )
- end
- end
-
describe '#to_reference' do
it { expect(triggered_alert.to_reference).to eq('') }
end
diff --git a/spec/models/project_services/ewm_service_spec.rb b/spec/models/project_services/ewm_service_spec.rb
new file mode 100644
index 00000000000..311c456569e
--- /dev/null
+++ b/spec/models/project_services/ewm_service_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EwmService do
+ describe 'Associations' do
+ it { is_expected.to belong_to :project }
+ it { is_expected.to have_one :service_hook }
+ end
+
+ describe 'Validations' do
+ context 'when service is active' do
+ before do
+ subject.active = true
+ end
+
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to validate_presence_of(:issues_url) }
+ it { is_expected.to validate_presence_of(:new_issue_url) }
+ it_behaves_like 'issue tracker service URL attribute', :project_url
+ it_behaves_like 'issue tracker service URL attribute', :issues_url
+ it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+ end
+
+ context 'when service is inactive' do
+ before do
+ subject.active = false
+ end
+
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.not_to validate_presence_of(:issues_url) }
+ it { is_expected.not_to validate_presence_of(:new_issue_url) }
+ end
+ end
+
+ describe "ReferencePatternValidation" do
+ it "extracts bug" do
+ expect(described_class.reference_pattern.match("This is bug 123")[:issue]).to eq("bug 123")
+ end
+
+ it "extracts task" do
+ expect(described_class.reference_pattern.match("This is task 123.")[:issue]).to eq("task 123")
+ end
+
+ it "extracts work item" do
+ expect(described_class.reference_pattern.match("This is work item 123 now")[:issue]).to eq("work item 123")
+ end
+
+ it "extracts workitem" do
+ expect(described_class.reference_pattern.match("workitem 123 at the beginning")[:issue]).to eq("workitem 123")
+ end
+
+ it "extracts defect" do
+ expect(described_class.reference_pattern.match("This is defect 123 defect")[:issue]).to eq("defect 123")
+ end
+
+ it "extracts rtcwi" do
+ expect(described_class.reference_pattern.match("This is rtcwi 123")[:issue]).to eq("rtcwi 123")
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 546b869ffd9..77b8b22e3f3 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -61,6 +61,7 @@ RSpec.describe Project do
it { is_expected.to have_one(:youtrack_service) }
it { is_expected.to have_one(:custom_issue_tracker_service) }
it { is_expected.to have_one(:bugzilla_service) }
+ it { is_expected.to have_one(:ewm_service) }
it { is_expected.to have_one(:external_wiki_service) }
it { is_expected.to have_one(:confluence_service) }
it { is_expected.to have_one(:project_feature) }
diff --git a/spec/presenters/alert_management/alert_presenter_spec.rb b/spec/presenters/alert_management/alert_presenter_spec.rb
index 365dc84ff53..3b7920dfd5e 100644
--- a/spec/presenters/alert_management/alert_presenter_spec.rb
+++ b/spec/presenters/alert_management/alert_presenter_spec.rb
@@ -9,7 +9,14 @@ RSpec.describe AlertManagement::AlertPresenter do
{
'title' => 'Alert title',
'start_time' => '2020-04-27T10:10:22.265949279Z',
- 'custom' => { 'param' => 73 }
+ 'custom' => {
+ 'alert' => {
+ 'fields' => %w[one two]
+ }
+ },
+ 'yet' => {
+ 'another' => 73
+ }
}
end
@@ -37,7 +44,10 @@ RSpec.describe AlertManagement::AlertPresenter do
#### Alert Details
- **custom.param:** 73
+ **title:** Alert title#{markdown_line_break}
+ **start_time:** 2020-04-27T10:10:22.265949279Z#{markdown_line_break}
+ **custom.alert.fields:** ["one", "two"]#{markdown_line_break}
+ **yet.another:** 73
MARKDOWN
)
end
@@ -54,4 +64,17 @@ RSpec.describe AlertManagement::AlertPresenter do
expect(presenter.details_url).to match(%r{#{project.web_url}/-/alert_management/#{alert.iid}/details})
end
end
+
+ describe '#details' do
+ subject { presenter.details }
+
+ it 'renders the payload as inline hash' do
+ is_expected.to eq(
+ 'title' => 'Alert title',
+ 'start_time' => '2020-04-27T10:10:22.265949279Z',
+ 'custom.alert.fields' => %w[one two],
+ 'yet.another' => 73
+ )
+ end
+ end
end
diff --git a/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb b/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb
index 8ff51b9a8e1..74c77b70e5a 100644
--- a/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb
+++ b/spec/presenters/alert_management/prometheus_alert_presenter_spec.rb
@@ -38,7 +38,11 @@ RSpec.describe AlertManagement::PrometheusAlertPresenter do
#### Alert Details
- **custom annotation:** custom annotation value
+ **annotations.custom annotation:** custom annotation value#{markdown_line_break}
+ **annotations.gitlab_incident_markdown:** **`markdown example`**#{markdown_line_break}
+ **annotations.title:** Alert title#{markdown_line_break}
+ **startsAt:** 2020-04-27T10:10:22.265949279Z#{markdown_line_break}
+ **generatorURL:** http://8d467bd4607a:9090/graph?g0.expr=vector%281%29&g0.tab=1
---
diff --git a/spec/presenters/projects/prometheus/alert_presenter_spec.rb b/spec/presenters/projects/prometheus/alert_presenter_spec.rb
index 1c37e551385..4f1b1931fb9 100644
--- a/spec/presenters/projects/prometheus/alert_presenter_spec.rb
+++ b/spec/presenters/projects/prometheus/alert_presenter_spec.rb
@@ -60,21 +60,6 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
subject { presenter.issue_summary_markdown }
context 'without default payload' do
- it do
- is_expected.to eq(
- <<~MARKDOWN.chomp
- **Start time:** #{presenter.start_time}
-
- MARKDOWN
- )
- end
- end
-
- context 'with annotations' do
- before do
- payload['annotations'] = { 'title' => 'Alert Title', 'foo' => 'value1', 'bar' => 'value2' }
- end
-
it do
is_expected.to eq(
<<~MARKDOWN.chomp
@@ -82,15 +67,23 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
#### Alert Details
- **foo:** value1#{markdown_line_break}
- **bar:** value2
+ **startsAt:** #{presenter.starts_at_raw}
MARKDOWN
)
end
end
- context 'with full query' do
+ context 'with optional attributes' do
before do
+ payload['annotations'] = {
+ 'title' => 'Alert Title',
+ 'foo' => 'value1',
+ 'bar' => 'value2',
+ 'description' => 'Alert Description',
+ 'monitoring_tool' => 'monitoring_tool_name',
+ 'service' => 'service_name',
+ 'hosts' => ['http://localhost:3000', 'http://localhost:3001']
+ }
payload['generatorURL'] = 'http://host?g0.expr=query'
end
@@ -98,57 +91,44 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
is_expected.to eq(
<<~MARKDOWN.chomp
**Start time:** #{presenter.start_time}#{markdown_line_break}
- **full_query:** `query`
-
- MARKDOWN
- )
- end
- end
-
- context 'with the Generic Alert parameters' do
- let(:generic_alert_params) do
- {
- 'title' => 'The Generic Alert Title',
- 'description' => 'The Generic Alert Description',
- 'monitoring_tool' => 'monitoring_tool_name',
- 'service' => 'service_name',
- 'hosts' => ['http://localhost:3000', 'http://localhost:3001']
- }
- end
-
- before do
- payload['annotations'] = generic_alert_params
- end
-
- it do
- is_expected.to eq(
- <<~MARKDOWN.chomp
- **Start time:** #{presenter.start_time}#{markdown_line_break}
+ **full_query:** `query`#{markdown_line_break}
**Service:** service_name#{markdown_line_break}
**Monitoring tool:** monitoring_tool_name#{markdown_line_break}
**Hosts:** http://localhost:3000 http://localhost:3001
#### Alert Details
- **description:** The Generic Alert Description
+ **annotations.hosts:** ["http://localhost:3000", "http://localhost:3001"]#{markdown_line_break}
+ **annotations.service:** service_name#{markdown_line_break}
+ **annotations.monitoring_tool:** monitoring_tool_name#{markdown_line_break}
+ **annotations.description:** Alert Description#{markdown_line_break}
+ **annotations.bar:** value2#{markdown_line_break}
+ **annotations.foo:** value1#{markdown_line_break}
+ **annotations.title:** Alert Title#{markdown_line_break}
+ **generatorURL:** http://host?g0.expr=query#{markdown_line_break}
+ **startsAt:** #{presenter.starts_at_raw}
MARKDOWN
)
end
+ end
- context 'when hosts is a string' do
- before do
- payload['annotations'] = { 'hosts' => 'http://localhost:3000' }
- end
+ context 'when hosts is a string' do
+ before do
+ payload['annotations'] = { 'hosts' => 'http://localhost:3000' }
+ end
- it do
- is_expected.to eq(
- <<~MARKDOWN.chomp
- **Start time:** #{presenter.start_time}#{markdown_line_break}
- **Hosts:** http://localhost:3000
+ it do
+ is_expected.to eq(
+ <<~MARKDOWN.chomp
+ **Start time:** #{presenter.start_time}#{markdown_line_break}
+ **Hosts:** http://localhost:3000
- MARKDOWN
- )
- end
+ #### Alert Details
+
+ **annotations.hosts:** http://localhost:3000#{markdown_line_break}
+ **startsAt:** #{presenter.starts_at_raw}
+ MARKDOWN
+ )
end
end
@@ -156,14 +136,7 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
let(:starts_at) { '2018-03-12T09:06:00Z' }
shared_examples_for 'markdown with metrics embed' do
- let(:expected_markdown) do
- <<~MARKDOWN.chomp
- **Start time:** #{presenter.start_time}#{markdown_line_break}
- **full_query:** `avg(metric) > 1.0`
-
- [](#{presenter.metrics_dashboard_url})
- MARKDOWN
- end
+ let(:embed_regex) { /\n\[\]\(#{Regexp.quote(presenter.metrics_dashboard_url)}\)\z/ }
context 'without a starting time available' do
around do |example|
@@ -174,11 +147,11 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
payload.delete('startsAt')
end
- it { is_expected.to eq(expected_markdown) }
+ it { is_expected.to match(embed_regex) }
end
context 'with a starting time available' do
- it { is_expected.to eq(expected_markdown) }
+ it { is_expected.to match(embed_regex) }
end
end
@@ -208,12 +181,8 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
end
context 'when not enough information is present for an embed' do
- let(:expected_markdown) do
- <<~MARKDOWN.chomp
- **Start time:** #{presenter.start_time}#{markdown_line_break}
- **full_query:** `avg(metric) > 1.0`
-
- MARKDOWN
+ shared_examples_for 'does not include an embed' do
+ it { is_expected.not_to match(/\[\]\(.+\)/) }
end
context 'without title' do
@@ -221,7 +190,7 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
payload['annotations'].delete('title')
end
- it { is_expected.to eq(expected_markdown) }
+ it_behaves_like 'does not include an embed'
end
context 'without environment' do
@@ -229,22 +198,15 @@ RSpec.describe Projects::Prometheus::AlertPresenter do
payload['labels'].delete('gitlab_environment_name')
end
- it { is_expected.to eq(expected_markdown) }
+ it_behaves_like 'does not include an embed'
end
context 'without full_query' do
- let(:expected_markdown) do
- <<~MARKDOWN.chomp
- **Start time:** #{presenter.start_time}
-
- MARKDOWN
- end
-
before do
payload.delete('generatorURL')
end
- it { is_expected.to eq(expected_markdown) }
+ it_behaves_like 'does not include an embed'
end
end
end
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 7bb73e9664b..1ae9b0d548d 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -60,25 +60,10 @@ RSpec.describe API::Variables do
let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
context 'when filter[environment_scope] is not passed' do
- context 'FF ci_variables_api_filter_environment_scope is enabled' do
- it 'returns 409' do
- get api("/projects/#{project.id}/variables/key1", user)
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
-
- context 'FF ci_variables_api_filter_environment_scope is disabled' do
- before do
- stub_feature_flags(ci_variables_api_filter_environment_scope: false)
- end
-
- it 'returns random one' do
- get api("/projects/#{project.id}/variables/key1", user)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['key']).to eq('key1')
- end
+ expect(response).to have_gitlab_http_status(:conflict)
end
end
@@ -232,25 +217,10 @@ RSpec.describe API::Variables do
let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
context 'when filter[environment_scope] is not passed' do
- context 'FF ci_variables_api_filter_environment_scope is enabled' do
- it 'returns 409' do
- get api("/projects/#{project.id}/variables/key1", user)
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
-
- context 'FF ci_variables_api_filter_environment_scope is disabled' do
- before do
- stub_feature_flags(ci_variables_api_filter_environment_scope: false)
- end
-
- it 'updates random one' do
- put api("/projects/#{project.id}/variables/key1", user), params: { value: 'new_val' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['value']).to eq('new_val')
- end
+ expect(response).to have_gitlab_http_status(:conflict)
end
end
@@ -312,26 +282,10 @@ RSpec.describe API::Variables do
let!(:var2) { create(:ci_variable, project: project, key: 'key1', environment_scope: 'production') }
context 'when filter[environment_scope] is not passed' do
- context 'FF ci_variables_api_filter_environment_scope is enabled' do
- it 'returns 409' do
- get api("/projects/#{project.id}/variables/key1", user)
+ it 'returns 409' do
+ get api("/projects/#{project.id}/variables/key1", user)
- expect(response).to have_gitlab_http_status(:conflict)
- end
- end
-
- context 'FF ci_variables_api_filter_environment_scope is disabled' do
- before do
- stub_feature_flags(ci_variables_api_filter_environment_scope: false)
- end
-
- it 'deletes random one' do
- expect do
- delete api("/projects/#{project.id}/variables/key1", user), params: { 'filter[environment_scope]': 'production' }
-
- expect(response).to have_gitlab_http_status(:no_content)
- end.to change {project.variables.count}.by(-1)
- end
+ expect(response).to have_gitlab_http_status(:conflict)
end
end
diff --git a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
index 8dbd90afa5e..fa4acc62226 100644
--- a/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
+++ b/spec/rubocop/cop/migration/create_table_with_foreign_keys_spec.rb
@@ -42,14 +42,28 @@ RSpec.describe RuboCop::Cop::Migration::CreateTableWithForeignKeys, type: :ruboc
context 'with foreign key' do
context 'with just one foreign key' do
context 'when the foreign_key targets a high traffic table' do
- it 'does not register any offenses' do
- expect_no_offenses(<<~RUBY)
- def up
- create_table(:foo) do |t|
- t.references :project, "foreign_key" => { on_delete: 'cascade', to_table: 'projects' }
+ context 'when the foreign_key has to_table option set' do
+ it 'does not register any offenses' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ create_table(:foo) do |t|
+ t.references :project, "foreign_key" => { on_delete: 'cascade', to_table: 'projects' }
+ end
end
- end
- RUBY
+ RUBY
+ end
+ end
+
+ context 'when the foreign_key does not have to_table option set' do
+ it 'does not register any offenses' do
+ expect_no_offenses(<<~RUBY)
+ def up
+ create_table(:foo) do |t|
+ t.references :project, foreign_key: { on_delete: 'cascade' }
+ end
+ end
+ RUBY
+ end
end
end
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index 487c38da7da..c9910487798 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -124,3 +124,16 @@ end
def get_textarea_height
page.evaluate_script('document.getElementById("merge_request_description").offsetHeight')
end
+
+RSpec.shared_examples 'an editable merge request with reviewers' do
+ it 'updates merge request', :js do
+ find('.js-reviewer-search').click
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="merge_request[reviewer_ids][]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-reviewer-search' do
+ expect(page).to have_content user.name
+ end
+ end
+end
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 55a74dc8229..215d404e395 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe 'projects/merge_requests/edit.html.haml' do
target_project: project,
author: user,
assignees: [user],
+ reviewers: [user],
milestone: milestone)
end