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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-22 18:09:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-22 18:09:48 +0300
commit640007842a876dfa551578feccfd0fe2307c522a (patch)
tree4204c45a13b9beac3040df00572ffe0ecdb0ca40
parent421f6c92d5984d035a7a6687d70277ba88f5f92b (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/batch_comments/components/preview_dropdown.vue14
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js6
-rw-r--r--app/assets/javascripts/milestones/milestone.js12
-rw-r--r--app/assets/javascripts/tabs/constants.js3
-rw-r--r--app/assets/javascripts/tabs/index.js23
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue2
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/services/bulk_imports/create_pipeline_trackers_service.rb2
-rw-r--r--app/services/issues/create_service.rb12
-rw-r--r--app/views/groups/settings/_general.html.haml2
-rw-r--r--app/views/notify/_reassigned_issuable_email.html.haml11
-rw-r--r--app/views/notify/_relabeled_issuable_email.html.haml3
-rw-r--r--app/views/profiles/_email_settings.html.haml4
-rw-r--r--app/views/profiles/_name.html.haml4
-rw-r--r--app/views/profiles/show.html.haml18
-rw-r--r--config/feature_flags/development/contacts_autocomplete.yml8
-rw-r--r--config/routes/project.rb14
-rw-r--r--doc/development/documentation/restful_api_styleguide.md11
-rw-r--r--doc/user/crm/index.md8
-rw-r--r--lib/backup/gitaly_backup.rb4
-rw-r--r--lib/gitlab/ci/runner_upgrade_check.rb24
-rw-r--r--lib/gitlab/email/handler/service_desk_handler.rb5
-rw-r--r--lib/gitlab/version_info.rb16
-rw-r--r--lib/gitlab/x509/certificate.rb21
-rw-r--r--lib/gitlab/x509/signature.rb2
-rw-r--r--locale/gitlab.pot38
-rw-r--r--spec/fixtures/emails/service_desk.eml1
-rw-r--r--spec/frontend/batch_comments/components/preview_dropdown_spec.js26
-rw-r--r--spec/frontend/lib/utils/common_utils_spec.js22
-rw-r--r--spec/frontend/tabs/index_spec.js105
-rw-r--r--spec/frontend/vue_shared/components/markdown/field_spec.js5
-rw-r--r--spec/lib/backup/gitaly_backup_spec.rb4
-rw-r--r--spec/lib/gitlab/email/handler/service_desk_handler_spec.rb18
-rw-r--r--spec/lib/gitlab/version_info_spec.rb49
-rw-r--r--spec/lib/gitlab/x509/certificate_spec.rb62
-rw-r--r--spec/lib/gitlab/x509/signature_spec.rb2
-rw-r--r--spec/routing/project_routing_spec.rb24
39 files changed, 433 insertions, 157 deletions
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 26c7306c880..86f5ef48522 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -5,7 +5,7 @@ review-cleanup:
extends:
- .default-retry
- .review:rules:review-cleanup
- image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:gitlab-helm3.5-kubectl1.17
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:gitlab-gcloud-helm3.5-kubectl1.17
stage: prepare
environment:
name: review/${CI_COMMIT_REF_SLUG}${FREQUENCY}
diff --git a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
index f839056daf8..770a5b58276 100644
--- a/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
+++ b/app/assets/javascripts/batch_comments/components/preview_dropdown.vue
@@ -2,6 +2,7 @@
import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import { setUrlParams, visitUrl } from '~/lib/utils/url_utility';
import PreviewItem from './preview_item.vue';
import DraftsCount from './drafts_count.vue';
@@ -17,6 +18,7 @@ export default {
computed: {
...mapState('diffs', ['viewDiffsFileByFile']),
...mapGetters('batchComments', ['draftsCount', 'sortedDrafts']),
+ ...mapGetters(['getNoteableData']),
},
methods: {
...mapActions('diffs', ['setCurrentFileHash']),
@@ -24,12 +26,21 @@ export default {
isLast(index) {
return index === this.sortedDrafts.length - 1;
},
+ isOnLatestDiff(draft) {
+ return draft.position?.head_sha === this.getNoteableData.diff_head_sha;
+ },
async onClickDraft(draft) {
if (this.viewDiffsFileByFile && draft.file_hash) {
await this.setCurrentFileHash(draft.file_hash);
}
- await this.scrollToDraft(draft);
+ if (draft.position && !this.isOnLatestDiff(draft)) {
+ const url = new URL(setUrlParams({ commit_id: draft.position.head_sha }));
+ url.hash = `note_${draft.id}`;
+ visitUrl(url.toString());
+ } else {
+ await this.scrollToDraft(draft);
+ }
},
},
};
@@ -52,6 +63,7 @@ export default {
data-testid="preview-item"
@click="onClickDraft(draft)"
>
+ {{ isOnLatestDiff(draft) }}
<preview-item :draft="draft" :is-last="isLast(index)" />
</gl-dropdown-item>
</gl-dropdown>
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 1ed0cc3130b..eb50ea62d06 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -11,6 +11,8 @@ import { convertToCamelCase, convertToSnakeCase } from './text_utility';
import { isObject } from './type_utility';
import { getLocationHash } from './url_utility';
+export const NO_SCROLL_TO_HASH_CLASS = 'js-no-scroll-to-hash';
+
export const getPagePath = (index = 0) => {
const { page = '' } = document.body.dataset;
return page.split(':')[index];
@@ -68,6 +70,10 @@ export const handleLocationHash = () => {
hash = decodeURIComponent(hash);
const target = document.getElementById(hash) || document.getElementById(`user-content-${hash}`);
+
+ // Allow targets to opt out of scroll behavior
+ if (target?.classList.contains(NO_SCROLL_TO_HASH_CLASS)) return;
+
const fixedTabs = document.querySelector('.js-tabs-affix');
const fixedDiffStats = document.querySelector('.js-diff-files-changed');
const fixedNav = document.querySelector('.navbar-gitlab');
diff --git a/app/assets/javascripts/milestones/milestone.js b/app/assets/javascripts/milestones/milestone.js
index 05102f73f92..8f2721c2a5b 100644
--- a/app/assets/javascripts/milestones/milestone.js
+++ b/app/assets/javascripts/milestones/milestone.js
@@ -1,33 +1,27 @@
import createFlash from '~/flash';
import { sanitize } from '~/lib/dompurify';
import axios from '~/lib/utils/axios_utils';
-import { historyPushState } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
-import { GlTabsBehavior, TAB_SHOWN_EVENT } from '~/tabs';
+import { GlTabsBehavior, TAB_SHOWN_EVENT, HISTORY_TYPE_HASH } from '~/tabs';
export default class Milestone {
constructor() {
this.tabsEl = document.querySelector('.js-milestone-tabs');
- this.glTabs = new GlTabsBehavior(this.tabsEl);
this.loadedTabs = new WeakSet();
this.bindTabsSwitching();
- this.loadInitialTab();
+ // eslint-disable-next-line no-new
+ new GlTabsBehavior(this.tabsEl, { history: HISTORY_TYPE_HASH });
}
bindTabsSwitching() {
this.tabsEl.addEventListener(TAB_SHOWN_EVENT, (event) => {
const tab = event.target;
const { activeTabPanel } = event.detail;
- historyPushState(tab.getAttribute('href'));
this.loadTab(tab, activeTabPanel);
});
}
- loadInitialTab() {
- const tab = this.tabsEl.querySelector(`a[href="${window.location.hash}"]`);
- this.glTabs.activateTab(tab || this.glTabs.activeTab);
- }
loadTab(tab, tabPanel) {
const { endpoint } = tab.dataset;
diff --git a/app/assets/javascripts/tabs/constants.js b/app/assets/javascripts/tabs/constants.js
index 90c9a89d652..0c227ab7afc 100644
--- a/app/assets/javascripts/tabs/constants.js
+++ b/app/assets/javascripts/tabs/constants.js
@@ -14,3 +14,6 @@ export const ATTR_ROLE = 'role';
export const ATTR_TABINDEX = 'tabindex';
export const TAB_SHOWN_EVENT = 'gl-tab-shown';
+
+export const HISTORY_TYPE_HASH = 'hash';
+export const ALLOWED_HISTORY_TYPES = [HISTORY_TYPE_HASH];
diff --git a/app/assets/javascripts/tabs/index.js b/app/assets/javascripts/tabs/index.js
index 44937e593e0..9230b7361a5 100644
--- a/app/assets/javascripts/tabs/index.js
+++ b/app/assets/javascripts/tabs/index.js
@@ -1,4 +1,5 @@
import { uniqueId } from 'lodash';
+import { historyReplaceState, NO_SCROLL_TO_HASH_CLASS } from '~/lib/utils/common_utils';
import {
ACTIVE_TAB_CLASSES,
ATTR_ROLE,
@@ -12,9 +13,11 @@ import {
KEY_CODE_RIGHT,
KEY_CODE_DOWN,
TAB_SHOWN_EVENT,
+ HISTORY_TYPE_HASH,
+ ALLOWED_HISTORY_TYPES,
} from './constants';
-export { TAB_SHOWN_EVENT };
+export { TAB_SHOWN_EVENT, HISTORY_TYPE_HASH };
/**
* The `GlTabsBehavior` class adds interactivity to tabs created by the `gl_tabs_nav` and
@@ -88,9 +91,13 @@ export class GlTabsBehavior {
/**
* Create a GlTabsBehavior instance.
*
- * @param {HTMLElement} el The element created by the Rails `gl_tabs_nav` helper.
+ * @param {HTMLElement} el - The element created by the Rails `gl_tabs_nav` helper.
+ * @param {Object} [options]
+ * @param {'hash' | null} [options.history=null] - Sets the type of routing GlTabs will use when navigating between tabs.
+ * 'hash': Updates the URL hash with the current tab ID.
+ * null: No routing mechanism will be used.
*/
- constructor(el) {
+ constructor(el, { history = null } = {}) {
if (!el) {
throw new Error('Cannot instantiate GlTabsBehavior without an element');
}
@@ -100,8 +107,11 @@ export class GlTabsBehavior {
this.tabs = this.getTabs();
this.activeTab = null;
+ this.history = ALLOWED_HISTORY_TYPES.includes(history) ? history : null;
+
this.setAccessibilityAttrs();
this.bindEvents();
+ if (this.history === HISTORY_TYPE_HASH) this.loadInitialTab();
}
setAccessibilityAttrs() {
@@ -128,6 +138,7 @@ export class GlTabsBehavior {
tab.setAttribute(ATTR_ARIA_CONTROLS, tabPanel.id);
}
+ tabPanel.classList.add(NO_SCROLL_TO_HASH_CLASS);
tabPanel.setAttribute(ATTR_ROLE, 'tabpanel');
tabPanel.setAttribute(ATTR_ARIA_LABELLEDBY, tab.id);
});
@@ -164,6 +175,11 @@ export class GlTabsBehavior {
});
}
+ loadInitialTab() {
+ const tab = this.tabList.querySelector(`a[href="${CSS.escape(window.location.hash)}"]`);
+ this.activateTab(tab || this.activeTab);
+ }
+
activatePreviousTab() {
const currentTabIndex = this.tabs.indexOf(this.activeTab);
@@ -216,6 +232,7 @@ export class GlTabsBehavior {
const tabPanel = this.getPanelForTab(tabToActivate);
tabPanel.classList.add(ACTIVE_PANEL_CLASS);
+ if (this.history === HISTORY_TYPE_HASH) historyReplaceState(tabToActivate.getAttribute('href'));
this.activeTab = tabToActivate;
this.dispatchTabShown(tabToActivate, tabPanel);
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 1f309a19b14..32b3a0e22c2 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -248,7 +248,7 @@ export default {
labels: this.enableAutocomplete,
snippets: this.enableAutocomplete,
vulnerabilities: this.enableAutocomplete,
- contacts: this.enableAutocomplete && this.glFeatures.contactsAutocomplete,
+ contacts: this.enableAutocomplete,
},
true,
);
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index f974b16468c..255d77988f2 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -41,7 +41,6 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_download_code!, only: [:related_branches]
before_action do
- push_frontend_feature_flag(:contacts_autocomplete, project&.group)
push_frontend_feature_flag(:incident_timeline, project)
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index e46aa6a446c..4ea2512bc67 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -15,7 +15,7 @@ module UsersHelper
end
def user_email_help_text(user)
- return 'We also use email for avatar detection if no avatar is uploaded' unless user.unconfirmed_email.present?
+ return 'We also use email for avatar detection if no avatar is uploaded.' unless user.unconfirmed_email.present?
confirmation_link = link_to 'Resend confirmation e-mail', user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
diff --git a/app/services/bulk_imports/create_pipeline_trackers_service.rb b/app/services/bulk_imports/create_pipeline_trackers_service.rb
index 5c9c68e62b5..af97aec09b5 100644
--- a/app/services/bulk_imports/create_pipeline_trackers_service.rb
+++ b/app/services/bulk_imports/create_pipeline_trackers_service.rb
@@ -47,7 +47,7 @@ module BulkImports
end
def non_patch_source_version
- Gitlab::VersionInfo.new(source_version.major, source_version.minor, 0)
+ source_version.without_patch
end
def log_skipped_pipeline(pipeline, minimum_version, maximum_version)
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index edf6d75b632..d4fbbf67420 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -13,6 +13,7 @@ module Issues
# in the caller (for example, an issue created via email) and the required arguments to the
# SpamParams constructor are not otherwise available, spam_params: must be explicitly passed as nil.
def initialize(project:, current_user: nil, params: {}, spam_params:, build_service: nil)
+ @extra_params = params.delete(:extra_params) || {}
super(project: project, current_user: current_user, params: params)
@spam_params = spam_params
@build_service = build_service || BuildService.new(project: project, current_user: current_user, params: params)
@@ -56,7 +57,7 @@ module Issues
handle_add_related_issue(issue)
resolve_discussions_with_issue(issue)
create_escalation_status(issue)
- try_to_associate_contact(issue)
+ try_to_associate_contacts(issue)
super
end
@@ -85,7 +86,7 @@ module Issues
private
- attr_reader :spam_params
+ attr_reader :spam_params, :extra_params
def create_escalation_status(issue)
::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
@@ -101,11 +102,14 @@ module Issues
IssueLinks::CreateService.new(issue, issue.author, { target_issuable: @add_related_issue }).execute
end
- def try_to_associate_contact(issue)
+ def try_to_associate_contacts(issue)
return unless issue.external_author
return unless current_user.can?(:set_issue_crm_contacts, issue)
- set_crm_contacts(issue, [issue.external_author])
+ contacts = [issue.external_author]
+ contacts.concat extra_params[:cc] unless extra_params[:cc].nil?
+
+ set_crm_contacts(issue, contacts)
end
end
end
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index ad0780e869c..3e3de5d60e0 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -1,6 +1,6 @@
= form_for @group, html: { multipart: true, class: 'gl-show-field-errors js-general-settings-form' }, authenticity_token: true do |f|
%input{ type: 'hidden', name: 'update_section', value: 'js-general-settings' }
- = form_errors(@group)
+ = form_errors(@group, pajamas_alert: true)
%fieldset
.row
diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml
index 4ab40ff2659..54e51e07c86 100644
--- a/app/views/notify/_reassigned_issuable_email.html.haml
+++ b/app/views/notify/_reassigned_issuable_email.html.haml
@@ -1,10 +1,7 @@
+- to_names = content_tag(:strong, issuable.assignees.any? ? sanitize_name(issuable.assignee_list) : s_('Unassigned'))
+
%p
- Assignee changed
- if previous_assignees.any?
- from
- %strong= sanitize_name(previous_assignees.map(&:name).to_sentence)
- to
- - if issuable.assignees.any?
- %strong= sanitize_name(issuable.assignee_list)
+ = html_escape(s_('Notify|Assignee changed from %{fromNames} to %{toNames}').html_safe % { fromNames: content_tag(:strong, sanitize_name(previous_assignees.map(&:name).to_sentence)), toNames: to_names })
- else
- %strong Unassigned
+ = html_escape(s_('Notify|Assignee changed to %{toNames}').html_safe % { toNames: to_names})
diff --git a/app/views/notify/_relabeled_issuable_email.html.haml b/app/views/notify/_relabeled_issuable_email.html.haml
index 80a0de255be..41d3a63845f 100644
--- a/app/views/notify/_relabeled_issuable_email.html.haml
+++ b/app/views/notify/_relabeled_issuable_email.html.haml
@@ -1,3 +1,2 @@
%p
- #{'Label'.pluralize(@label_names.size)} added:
- %em= @label_names.to_sentence
+ = html_escape(n_('Label added: %{labels}', 'Labels added: %{labels}', @label_names.size).html_safe % { labels: content_tag(:em, @label_names.to_sentence).html_safe })
diff --git a/app/views/profiles/_email_settings.html.haml b/app/views/profiles/_email_settings.html.haml
index 457d6690a78..0ca9acba2de 100644
--- a/app/views/profiles/_email_settings.html.haml
+++ b/app/views/profiles/_email_settings.html.haml
@@ -22,12 +22,12 @@
{ include_blank: s_("Profiles|Do not show on profile") },
{ class: 'gl-form-select custom-select', disabled: email_change_disabled }
%small.form-text.text-gl-muted
- = s_("Profiles|This email will be displayed on your public profile")
+ = s_("Profiles|This email will be displayed on your public profile.")
.form-group.gl-form-group
- commit_email_link_url = help_page_path('user/profile/index', anchor: 'change-the-email-displayed-on-your-commits', target: '_blank')
- commit_email_link_start = '<a href="%{url}">'.html_safe % { url: commit_email_link_url }
- - commit_email_docs_link = s_('Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}').html_safe % { commit_email_link_start: commit_email_link_start, commit_email_link_end: '</a>'.html_safe }
+ - commit_email_docs_link = s_('Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more.%{commit_email_link_end}').html_safe % { commit_email_link_start: commit_email_link_start, commit_email_link_end: '</a>'.html_safe }
= form.label :commit_email, s_('Profiles|Commit email')
.gl-md-form-input-lg
= form.select :commit_email,
diff --git a/app/views/profiles/_name.html.haml b/app/views/profiles/_name.html.haml
index 5af4fe24d62..d798eab7635 100644
--- a/app/views/profiles/_name.html.haml
+++ b/app/views/profiles/_name.html.haml
@@ -2,8 +2,8 @@
- if user.read_only_attribute?(:name)
= form.text_field :name, class: 'gl-form-input form-control', required: true, readonly: true
%small.form-text.text-gl-muted
- = s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you") % { provider_label: attribute_provider_label(:name) }
+ = s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you.") % { provider_label: attribute_provider_label(:name) }
- else
= form.text_field :name, class: 'gl-form-input form-control', required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead")
%small.form-text.text-gl-muted
- = s_("Profiles|Enter your name, so people you know can recognize you")
+ = s_("Profiles|Enter your name, so people you know can recognize you.")
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 9c467bf5485..343a6a39b24 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -74,7 +74,7 @@
.form-group.gl-form-group
= status_form.gitlab_ui_checkbox_component :availability,
s_("Profiles|Busy"),
- help_text: s_('Profiles|An indicator appears next to your name and avatar'),
+ help_text: s_('Profiles|An indicator appears next to your name and avatar.'),
checkbox_options: { data: { testid: "user-availability-checkbox" } },
checked_value: availability["busy"],
unchecked_value: availability["not_set"]
@@ -83,7 +83,7 @@
.row.user-time-preferences.js-search-settings-section
.col-lg-4.profile-settings-sidebar
%h4.gl-mt-0= s_("Profiles|Time settings")
- %p= s_("Profiles|Set your local time zone")
+ %p= s_("Profiles|Set your local time zone.")
.col-lg-8
%h5= _("Time zone")
= dropdown_tag(_("Select a time zone"), options: { toggle_class: 'gl-button btn js-timezone-dropdown input-lg gl-w-full!', title: _("Select a time zone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } )
@@ -95,7 +95,7 @@
%h4.gl-mt-0
= s_("Profiles|Main settings")
%p
- = s_("Profiles|This information will appear on your profile")
+ = s_("Profiles|This information will appear on your profile.")
- if current_user.ldap_user?
= s_("Profiles|Some options are unavailable for LDAP accounts")
.col-lg-8
@@ -109,12 +109,12 @@
= f.label :pronouns, s_('Profiles|Pronouns')
= f.text_field :pronouns, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
- = s_("Profiles|Enter your pronouns to let people know how to refer to you")
+ = s_("Profiles|Enter your pronouns to let people know how to refer to you.")
.form-group.gl-form-group
= f.label :pronunciation, s_('Profiles|Pronunciation')
= f.text_field :pronunciation, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
- = s_("Profiles|Enter how your name is pronounced to help people address you correctly")
+ = s_("Profiles|Enter how your name is pronounced to help people address you correctly.")
= render_if_exists 'profiles/extra_settings', form: f
= render_if_exists 'profiles/email_settings', form: f
.form-group.gl-form-group
@@ -146,17 +146,17 @@
= f.label :organization, s_('Profiles|Organization')
= f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg'
%small.form-text.text-gl-muted
- = s_("Profiles|Who you represent or work for")
+ = s_("Profiles|Who you represent or work for.")
.form-group.gl-form-group
= f.label :bio, s_('Profiles|Bio')
= f.text_area :bio, class: 'gl-form-input gl-form-textarea form-control', rows: 4, maxlength: 250
%small.form-text.text-gl-muted
- = s_("Profiles|Tell us about yourself in fewer than 250 characters")
+ = s_("Profiles|Tell us about yourself in fewer than 250 characters.")
%hr
%fieldset.form-group.gl-form-group
%legend.col-form-label.col-form-label
= _('Private profile')
- - private_profile_label = s_("Profiles|Don't display activity-related personal information on your profile")
+ - private_profile_label = s_("Profiles|Don't display activity-related personal information on your profile.")
- private_profile_help_link = link_to sprite_icon('question-o'), help_page_path('user/profile/index.md', anchor: 'make-your-user-profile-page-private')
= f.gitlab_ui_checkbox_component :private_profile, '%{private_profile_label} %{private_profile_help_link}'.html_safe % { private_profile_label: private_profile_label, private_profile_help_link: private_profile_help_link.html_safe }
%fieldset.form-group.gl-form-group
@@ -164,7 +164,7 @@
= s_("Profiles|Private contributions")
= f.gitlab_ui_checkbox_component :include_private_contributions,
s_('Profiles|Include private contributions on my profile'),
- help_text: s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information")
+ help_text: s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.")
%hr
= f.submit s_("Profiles|Update profile settings"), class: 'gl-button btn btn-confirm gl-mr-3 js-password-prompt-btn'
= link_to _("Cancel"), user_path(current_user), class: 'gl-button btn btn-default btn-cancel'
diff --git a/config/feature_flags/development/contacts_autocomplete.yml b/config/feature_flags/development/contacts_autocomplete.yml
deleted file mode 100644
index 2b0908e7343..00000000000
--- a/config/feature_flags/development/contacts_autocomplete.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: contacts_autocomplete
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79639
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/352123
-milestone: '14.8'
-type: development
-group: group::product planning
-default_enabled: true
diff --git a/config/routes/project.rb b/config/routes/project.rb
index afc06780471..ce642ecf9f6 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -221,20 +221,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- # Legacy routes for `/-/integrations` which are now in `/-/settings/integrations`.
- # Can be removed in 15.2, see https://gitlab.com/gitlab-org/gitlab/-/issues/334846
- resources :integrations, controller: 'settings/integrations', constraints: { id: %r{[^/]+} }, only: [:edit, :update] do
- member do
- put :test
- end
-
- resources :hook_logs, only: [:show], controller: 'settings/integration_hook_logs' do
- member do
- post :retry
- end
- end
- end
-
resources :boards, only: [:index, :show, :create, :update, :destroy], constraints: { id: /\d+/ } do
collection do
get :recent
diff --git a/doc/development/documentation/restful_api_styleguide.md b/doc/development/documentation/restful_api_styleguide.md
index 1f270a2b5ee..b083f68e52d 100644
--- a/doc/development/documentation/restful_api_styleguide.md
+++ b/doc/development/documentation/restful_api_styleguide.md
@@ -66,7 +66,8 @@ Supported attributes:
| `attribute` | datatype | **{dotted-circle}** No | Detailed description. |
| `attribute` | datatype | **{dotted-circle}** No | Detailed description. |
-Response body attributes:
+If successful, returns [`<status_code>`](../../api/index.md#status-codes) and the following
+response attributes:
| Attribute | Type | Description |
|:-------------------------|:---------|:----------------------|
@@ -151,6 +152,14 @@ For information about writing attribute descriptions, see the [GraphQL API descr
## Response body description
+Start the description with the following sentence, replacing `status code` with the
+relevant [HTTP status code](../../api/index.md#status-codes), for example:
+
+```markdown
+If successful, returns [`200 OK`](../../api/index.md#status-codes) and the
+following response attributes:
+```
+
Use the following table headers to describe the response bodies. Attributes should
always be in code blocks using backticks (`` ` ``).
diff --git a/doc/user/crm/index.md b/doc/user/crm/index.md
index b5287816052..04bb3e582b1 100644
--- a/doc/user/crm/index.md
+++ b/doc/user/crm/index.md
@@ -118,6 +118,9 @@ organizations using the GraphQL API.
## Issues
+If you use [Service Desk](../project/service_desk.md) and create issues from emails,
+issues are linked to contacts matching the email addresses in the sender and CC of the email.
+
### View issues linked to a contact
To view a contact's issues, select a contact from the issue sidebar, or:
@@ -170,10 +173,7 @@ API.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2256) in GitLab 14.8 [with a flag](../../administration/feature_flags.md) named `contacts_autocomplete`. Disabled by default.
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/352123) in GitLab 15.0.
-
-FLAG:
-On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `contacts_autocomplete`.
-On GitLab.com, this feature is available.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/352123) in GitLab 15.2. [Feature flag `contacts_autocomplete`](https://gitlab.com/gitlab-org/gitlab/-/issues/352123) removed.
When you use the `/add_contacts` or `/remove_contacts` quick actions, follow them with `[contact:` and an autocomplete list appears:
diff --git a/lib/backup/gitaly_backup.rb b/lib/backup/gitaly_backup.rb
index 077eabdd131..a995f308c2b 100644
--- a/lib/backup/gitaly_backup.rb
+++ b/lib/backup/gitaly_backup.rb
@@ -96,8 +96,8 @@ module Backup
def build_env
{
- 'SSL_CERT_FILE' => OpenSSL::X509::DEFAULT_CERT_FILE,
- 'SSL_CERT_DIR' => OpenSSL::X509::DEFAULT_CERT_DIR
+ 'SSL_CERT_FILE' => Gitlab::X509::Certificate.default_cert_file,
+ 'SSL_CERT_DIR' => Gitlab::X509::Certificate.default_cert_dir
}.merge(ENV)
end
diff --git a/lib/gitlab/ci/runner_upgrade_check.rb b/lib/gitlab/ci/runner_upgrade_check.rb
index 0808290fe5b..f265a40eb8b 100644
--- a/lib/gitlab/ci/runner_upgrade_check.rb
+++ b/lib/gitlab/ci/runner_upgrade_check.rb
@@ -25,18 +25,16 @@ module Gitlab
raise ArgumentError, "'#{orig_runner_version}' is not a valid version" unless runner_version.valid?
- gitlab_minor_version = version_without_patch(@gitlab_version)
+ gitlab_minor_version = @gitlab_version.without_patch
available_releases = releases
- .reject { |release| release.major > @gitlab_version.major }
- .reject do |release|
- release_minor_version = version_without_patch(release)
+ .reject { |release| release.major > @gitlab_version.major }
+ .reject do |release|
+ # Do not reject a patch update, even if the runner is ahead of the instance version
+ next false if release.same_minor_version?(runner_version)
- # Do not reject a patch update, even if the runner is ahead of the instance version
- next false if version_without_patch(runner_version) == release_minor_version
-
- release_minor_version > gitlab_minor_version
- end
+ release.without_patch > gitlab_minor_version
+ end
return :recommended if available_releases.any? { |available_rel| patch_update?(available_rel, runner_version) }
return :recommended if outside_backport_window?(runner_version, releases)
@@ -63,19 +61,15 @@ module Gitlab
def outside_backport_window?(runner_version, releases)
return false if runner_version >= releases.last # return early if runner version is too new
- latest_minor_releases = releases.map { |r| version_without_patch(r) }.uniq { |v| v.to_s }
+ latest_minor_releases = releases.map(&:without_patch).uniq
latest_version_position = latest_minor_releases.count - 1
- runner_version_position = latest_minor_releases.index(version_without_patch(runner_version))
+ runner_version_position = latest_minor_releases.index(runner_version.without_patch)
return true if runner_version_position.nil? # consider outside if version is too old
# https://docs.gitlab.com/ee/policy/maintenance.html#backporting-to-older-releases
latest_version_position - runner_version_position > 2
end
-
- def version_without_patch(version)
- ::Gitlab::VersionInfo.new(version.major, version.minor, 0)
- end
end
end
end
diff --git a/lib/gitlab/email/handler/service_desk_handler.rb b/lib/gitlab/email/handler/service_desk_handler.rb
index 71b1d4ed8f9..8e2c7559bc1 100644
--- a/lib/gitlab/email/handler/service_desk_handler.rb
+++ b/lib/gitlab/email/handler/service_desk_handler.rb
@@ -98,7 +98,10 @@ module Gitlab
title: mail.subject,
description: message_including_template,
confidential: true,
- external_author: from_address
+ external_author: from_address,
+ extra_params: {
+ cc: mail.cc
+ }
},
spam_params: nil
).execute
diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb
index aa6d5310161..88a5b735d8c 100644
--- a/lib/gitlab/version_info.rb
+++ b/lib/gitlab/version_info.rb
@@ -52,5 +52,21 @@ module Gitlab
def valid?
@major >= 0 && @minor >= 0 && @patch >= 0 && @major + @minor + @patch > 0
end
+
+ def hash
+ [self.class, to_s].hash
+ end
+
+ def eql?(other)
+ (self <=> other) == 0
+ end
+
+ def same_minor_version?(other)
+ @major == other.major && @minor == other.minor
+ end
+
+ def without_patch
+ self.class.new(@major, @minor, 0)
+ end
end
end
diff --git a/lib/gitlab/x509/certificate.rb b/lib/gitlab/x509/certificate.rb
index 752f3c6b004..98688f504eb 100644
--- a/lib/gitlab/x509/certificate.rb
+++ b/lib/gitlab/x509/certificate.rb
@@ -23,6 +23,18 @@ module Gitlab
include ::Gitlab::Utils::StrongMemoize
end
+ def self.default_cert_dir
+ strong_memoize(:default_cert_dir) do
+ ENV.fetch('SSL_CERT_DIR', OpenSSL::X509::DEFAULT_CERT_DIR)
+ end
+ end
+
+ def self.default_cert_file
+ strong_memoize(:default_cert_file) do
+ ENV.fetch('SSL_CERT_FILE', OpenSSL::X509::DEFAULT_CERT_FILE)
+ end
+ end
+
def self.from_strings(key_string, cert_string, ca_certs_string = nil)
key = OpenSSL::PKey::RSA.new(key_string)
cert = OpenSSL::X509::Certificate.new(cert_string)
@@ -39,10 +51,10 @@ module Gitlab
# Returns all top-level, readable files in the default CA cert directory
def self.ca_certs_paths
- cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"].select do |path|
+ cert_paths = Dir["#{default_cert_dir}/*"].select do |path|
!File.directory?(path) && File.readable?(path)
end
- cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE
+ cert_paths << default_cert_file if File.exist? default_cert_file
cert_paths
end
@@ -61,6 +73,11 @@ module Gitlab
clear_memoization(:ca_certs_bundle)
end
+ def self.reset_default_cert_paths
+ clear_memoization(:default_cert_dir)
+ clear_memoization(:default_cert_file)
+ end
+
# Returns an array of OpenSSL::X509::Certificate objects, empty array if none found
#
# Ruby OpenSSL::X509::Certificate.new will only load the first
diff --git a/lib/gitlab/x509/signature.rb b/lib/gitlab/x509/signature.rb
index a6761e211fa..8acbfc144e9 100644
--- a/lib/gitlab/x509/signature.rb
+++ b/lib/gitlab/x509/signature.rb
@@ -59,7 +59,7 @@ module Gitlab
if Feature.enabled?(:x509_forced_cert_loading, type: :ops)
# Forcibly load the default cert file because the OpenSSL library seemingly ignores it
- store.add_file(OpenSSL::X509::DEFAULT_CERT_FILE) if File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
+ store.add_file(Gitlab::X509::Certificate.default_cert_file) if File.exist?(Gitlab::X509::Certificate.default_cert_file) # rubocop:disable Layout/LineLength
end
# valid_signing_time? checks the time attributes already
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4d740b154b5..bcc03be2aaa 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22411,6 +22411,11 @@ msgstr ""
msgid "Label actions dropdown"
msgstr ""
+msgid "Label added: %{labels}"
+msgid_plural "Labels added: %{labels}"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Label priority"
msgstr ""
@@ -26188,6 +26193,12 @@ msgstr ""
msgid "Notify|%{author_link}'s issue %{issue_reference_link} is due soon."
msgstr ""
+msgid "Notify|Assignee changed from %{fromNames} to %{toNames}"
+msgstr ""
+
+msgid "Notify|Assignee changed to %{toNames}"
+msgstr ""
+
msgid "Notify|Author: %{author_name}"
msgstr ""
@@ -29204,7 +29215,7 @@ msgstr ""
msgid "Profiles|An error occurred while updating your username, please try again."
msgstr ""
-msgid "Profiles|An indicator appears next to your name and avatar"
+msgid "Profiles|An indicator appears next to your name and avatar."
msgstr ""
msgid "Profiles|Avatar cropper"
@@ -29231,7 +29242,7 @@ msgstr ""
msgid "Profiles|Choose file..."
msgstr ""
-msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
+msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information."
msgstr ""
msgid "Profiles|City, country"
@@ -29273,7 +29284,7 @@ msgstr ""
msgid "Profiles|Do not show on profile"
msgstr ""
-msgid "Profiles|Don't display activity-related personal information on your profile"
+msgid "Profiles|Don't display activity-related personal information on your profile."
msgstr ""
msgid "Profiles|Edit Profile"
@@ -29282,16 +29293,16 @@ msgstr ""
msgid "Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place."
msgstr ""
-msgid "Profiles|Enter how your name is pronounced to help people address you correctly"
+msgid "Profiles|Enter how your name is pronounced to help people address you correctly."
msgstr ""
-msgid "Profiles|Enter your name, so people you know can recognize you"
+msgid "Profiles|Enter your name, so people you know can recognize you."
msgstr ""
msgid "Profiles|Enter your password to confirm the email change"
msgstr ""
-msgid "Profiles|Enter your pronouns to let people know how to refer to you"
+msgid "Profiles|Enter your pronouns to let people know how to refer to you."
msgstr ""
msgid "Profiles|Example: MacBook key"
@@ -29414,7 +29425,7 @@ msgstr ""
msgid "Profiles|Set new profile picture"
msgstr ""
-msgid "Profiles|Set your local time zone"
+msgid "Profiles|Set your local time zone."
msgstr ""
msgid "Profiles|Social sign-in"
@@ -29426,7 +29437,7 @@ msgstr ""
msgid "Profiles|Static object token was successfully reset"
msgstr ""
-msgid "Profiles|Tell us about yourself in fewer than 250 characters"
+msgid "Profiles|Tell us about yourself in fewer than 250 characters."
msgstr ""
msgid "Profiles|The ability to update your name has been disabled by your administrator."
@@ -29435,16 +29446,16 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr ""
-msgid "Profiles|This email will be displayed on your public profile"
+msgid "Profiles|This email will be displayed on your public profile."
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more.%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr ""
-msgid "Profiles|This information will appear on your profile"
+msgid "Profiles|This information will appear on your profile."
msgstr ""
msgid "Profiles|Time settings"
@@ -29489,7 +29500,7 @@ msgstr ""
msgid "Profiles|What's your status?"
msgstr ""
-msgid "Profiles|Who you represent or work for"
+msgid "Profiles|Who you represent or work for."
msgstr ""
msgid "Profiles|You can change your avatar here"
@@ -29531,6 +29542,9 @@ msgstr ""
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
msgstr ""
+msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you."
+msgstr ""
+
msgid "Profiles|Your status"
msgstr ""
diff --git a/spec/fixtures/emails/service_desk.eml b/spec/fixtures/emails/service_desk.eml
index 0db1270bc64..102f29542ae 100644
--- a/spec/fixtures/emails/service_desk.eml
+++ b/spec/fixtures/emails/service_desk.eml
@@ -6,6 +6,7 @@ Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: incoming+email-test-project_id-issue-@appmail.adventuretime.ooo
+Cc: Carbon Copy <cc@example.com>, kk@example.org
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: The message subject! @all
Mime-Version: 1.0
diff --git a/spec/frontend/batch_comments/components/preview_dropdown_spec.js b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
index bf3bbf4de26..079b64225e4 100644
--- a/spec/frontend/batch_comments/components/preview_dropdown_spec.js
+++ b/spec/frontend/batch_comments/components/preview_dropdown_spec.js
@@ -1,8 +1,15 @@
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { TEST_HOST } from 'helpers/test_constants';
+import { visitUrl } from '~/lib/utils/url_utility';
import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
+jest.mock('~/lib/utils/url_utility', () => ({
+ visitUrl: jest.fn(),
+ setUrlParams: jest.requireActual('~/lib/utils/url_utility').setUrlParams,
+}));
+
Vue.use(Vuex);
let wrapper;
@@ -27,6 +34,11 @@ function factory({ viewDiffsFileByFile = false, draftsCount = 1, sortedDrafts =
actions: { scrollToDraft },
getters: { draftsCount: () => draftsCount, sortedDrafts: () => sortedDrafts },
},
+ notes: {
+ getters: {
+ getNoteableData: () => ({ diff_head_sha: '123' }),
+ },
+ },
},
});
@@ -67,5 +79,19 @@ describe('Batch comments preview dropdown', () => {
expect(scrollToDraft).toHaveBeenCalledWith(expect.anything(), { id: 1 });
});
+
+ it('changes window location to navigate to commit', async () => {
+ factory({
+ viewDiffsFileByFile: false,
+ sortedDrafts: [{ id: 1, position: { head_sha: '1234' } }],
+ });
+
+ wrapper.findByTestId('preview-item').vm.$emit('click');
+
+ await nextTick();
+
+ expect(scrollToDraft).not.toHaveBeenCalled();
+ expect(visitUrl).toHaveBeenCalledWith(`${TEST_HOST}/?commit_id=1234#note_1`);
+ });
});
});
diff --git a/spec/frontend/lib/utils/common_utils_spec.js b/spec/frontend/lib/utils/common_utils_spec.js
index 8e499844406..7cf101a5e59 100644
--- a/spec/frontend/lib/utils/common_utils_spec.js
+++ b/spec/frontend/lib/utils/common_utils_spec.js
@@ -88,6 +88,28 @@ describe('common_utils', () => {
expectGetElementIdToHaveBeenCalledWith('user-content-definição');
});
+ it(`does not scroll when ${commonUtils.NO_SCROLL_TO_HASH_CLASS} is set on target`, () => {
+ jest.spyOn(window, 'scrollBy');
+
+ document.body.innerHTML += `
+ <div id="parent">
+ <a href="#test">Link</a>
+ <div style="height: 2000px;"></div>
+ <div id="test" style="height: 2000px;" class="${commonUtils.NO_SCROLL_TO_HASH_CLASS}"></div>
+ </div>
+ `;
+
+ window.history.pushState({}, null, '#test');
+ commonUtils.handleLocationHash();
+ jest.runOnlyPendingTimers();
+
+ try {
+ expect(window.scrollBy).not.toHaveBeenCalled();
+ } finally {
+ document.getElementById('parent').remove();
+ }
+ });
+
it('scrolls element into view', () => {
document.body.innerHTML += `
<div id="parent">
diff --git a/spec/frontend/tabs/index_spec.js b/spec/frontend/tabs/index_spec.js
index 67e3d707adb..1d61d38a488 100644
--- a/spec/frontend/tabs/index_spec.js
+++ b/spec/frontend/tabs/index_spec.js
@@ -1,9 +1,16 @@
-import { GlTabsBehavior, TAB_SHOWN_EVENT } from '~/tabs';
+import { GlTabsBehavior, TAB_SHOWN_EVENT, HISTORY_TYPE_HASH } from '~/tabs';
import { ACTIVE_PANEL_CLASS, ACTIVE_TAB_CLASSES } from '~/tabs/constants';
+import { getLocationHash } from '~/lib/utils/url_utility';
+import { NO_SCROLL_TO_HASH_CLASS } from '~/lib/utils/common_utils';
import { getFixture, setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
+import setWindowLocation from 'helpers/set_window_location_helper';
const tabsFixture = getFixture('tabs/tabs.html');
+global.CSS = {
+ escape: (val) => val,
+};
+
describe('GlTabsBehavior', () => {
let glTabs;
let tabShownEventSpy;
@@ -41,6 +48,7 @@ describe('GlTabsBehavior', () => {
});
expect(panel.classList.contains(ACTIVE_PANEL_CLASS)).toBe(true);
+ expect(panel.classList.contains(NO_SCROLL_TO_HASH_CLASS)).toBe(true);
};
const expectInactiveTabAndPanel = (name) => {
@@ -67,6 +75,7 @@ describe('GlTabsBehavior', () => {
});
expect(panel.classList.contains(ACTIVE_PANEL_CLASS)).toBe(false);
+ expect(panel.classList.contains(NO_SCROLL_TO_HASH_CLASS)).toBe(true);
};
const expectGlTabShownEvent = (name) => {
@@ -263,4 +272,98 @@ describe('GlTabsBehavior', () => {
expectInactiveTabAndPanel('foo');
});
});
+
+ describe('using history=hash', () => {
+ const defaultTab = 'foo';
+ let tab;
+ let tabsEl;
+
+ beforeEach(() => {
+ setHTMLFixture(tabsFixture);
+ tabsEl = findByTestId('tabs');
+ });
+
+ afterEach(() => {
+ glTabs.destroy();
+ resetHTMLFixture();
+ });
+
+ describe('when a hash exists onInit', () => {
+ beforeEach(() => {
+ tab = 'bar';
+ setWindowLocation(`http://foo.com/index#${tab}`);
+ glTabs = new GlTabsBehavior(tabsEl, { history: HISTORY_TYPE_HASH });
+ });
+
+ it('sets the active tab to the hash and preserves hash', () => {
+ expectActiveTabAndPanel(tab);
+ expect(getLocationHash()).toBe(tab);
+ });
+ });
+
+ describe('when a hash does not exist onInit', () => {
+ beforeEach(() => {
+ setWindowLocation(`http://foo.com/index`);
+ glTabs = new GlTabsBehavior(tabsEl, { history: HISTORY_TYPE_HASH });
+ });
+
+ it('sets the active tab to the first tab and sets hash', () => {
+ expectActiveTabAndPanel(defaultTab);
+ expect(getLocationHash()).toBe(defaultTab);
+ });
+ });
+
+ describe('clicking on an inactive tab', () => {
+ beforeEach(() => {
+ tab = 'qux';
+ setWindowLocation(`http://foo.com/index`);
+ glTabs = new GlTabsBehavior(tabsEl, { history: HISTORY_TYPE_HASH });
+
+ findTab(tab).click();
+ });
+
+ it('changes the tabs and updates the hash', () => {
+ expectInactiveTabAndPanel(defaultTab);
+ expectActiveTabAndPanel(tab);
+ expect(getLocationHash()).toBe(tab);
+ });
+ });
+
+ describe('keyboard navigation', () => {
+ const secondTab = 'bar';
+
+ beforeEach(() => {
+ setWindowLocation(`http://foo.com/index`);
+ glTabs = new GlTabsBehavior(tabsEl, { history: HISTORY_TYPE_HASH });
+ });
+
+ it.each(['ArrowRight', 'ArrowDown'])(
+ 'pressing %s moves to next tab and updates hash',
+ (code) => {
+ expectActiveTabAndPanel(defaultTab);
+
+ triggerKeyDown(code, glTabs.activeTab);
+
+ expectInactiveTabAndPanel(defaultTab);
+ expectActiveTabAndPanel(secondTab);
+ expect(getLocationHash()).toBe(secondTab);
+ },
+ );
+
+ it.each(['ArrowLeft', 'ArrowUp'])(
+ 'pressing %s moves to previous tab and updates hash',
+ (code) => {
+ // First, make the 2nd tab active
+ findTab(secondTab).click();
+ expectActiveTabAndPanel(secondTab);
+
+ triggerKeyDown(code, glTabs.activeTab);
+
+ expectInactiveTabAndPanel(secondTab);
+ expectActiveTabAndPanel(defaultTab);
+ expect(getLocationHash()).toBe(defaultTab);
+ },
+ );
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/markdown/field_spec.js b/spec/frontend/vue_shared/components/markdown/field_spec.js
index b3376f26a25..85a135d2b89 100644
--- a/spec/frontend/vue_shared/components/markdown/field_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/field_spec.js
@@ -67,11 +67,6 @@ describe('Markdown field component', () => {
enablePreview,
restrictedToolBarItems,
},
- provide: {
- glFeatures: {
- contactsAutocomplete: true,
- },
- },
},
);
}
diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb
index ab198fcbe1f..3a9c4dfe3fb 100644
--- a/spec/lib/backup/gitaly_backup_spec.rb
+++ b/spec/lib/backup/gitaly_backup_spec.rb
@@ -16,8 +16,8 @@ RSpec.describe Backup::GitalyBackup do
let(:expected_env) do
{
- 'SSL_CERT_FILE' => OpenSSL::X509::DEFAULT_CERT_FILE,
- 'SSL_CERT_DIR' => OpenSSL::X509::DEFAULT_CERT_DIR
+ 'SSL_CERT_FILE' => Gitlab::X509::Certificate.default_cert_file,
+ 'SSL_CERT_DIR' => Gitlab::X509::Certificate.default_cert_dir
}.merge(ENV)
end
diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
index 6e7806c5d53..d0aba70081b 100644
--- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb
@@ -52,14 +52,6 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
expect(new_issue.issue_email_participants.first.email).to eq(author_email)
end
- it 'attaches existing CRM contact' do
- contact = create(:contact, group: group, email: author_email)
- receiver.execute
- new_issue = Issue.last
-
- expect(new_issue.issue_customer_relations_contacts.last.contact).to eq(contact)
- end
-
it 'sends thank you email' do
expect { receiver.execute }.to have_enqueued_job.on_queue('mailers')
end
@@ -77,6 +69,16 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
context 'when everything is fine' do
it_behaves_like 'a new issue request'
+ it 'attaches existing CRM contacts' do
+ contact = create(:contact, group: group, email: author_email)
+ contact2 = create(:contact, group: group, email: "cc@example.com")
+ contact3 = create(:contact, group: group, email: "kk@example.org")
+ receiver.execute
+ new_issue = Issue.last
+
+ expect(new_issue.issue_customer_relations_contacts.map(&:contact)).to contain_exactly(contact, contact2, contact3)
+ end
+
context 'with legacy incoming email address' do
let(:email_raw) { fixture_file('emails/service_desk_legacy.eml') }
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index f81e3aa070a..a77d51fab88 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'spec_helper'
+require 'fast_spec_helper'
RSpec.describe 'Gitlab::VersionInfo' do
before do
@@ -13,7 +13,7 @@ RSpec.describe 'Gitlab::VersionInfo' do
@v2_0_0 = Gitlab::VersionInfo.new(2, 0, 0)
end
- context '>' do
+ describe '>' do
it { expect(@v2_0_0).to be > @v1_1_0 }
it { expect(@v1_1_0).to be > @v1_0_1 }
it { expect(@v1_0_1).to be > @v1_0_0 }
@@ -21,12 +21,12 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v0_1_0).to be > @v0_0_1 }
end
- context '>=' do
+ describe '>=' do
it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) }
it { expect(@v2_0_0).to be >= @v1_1_0 }
end
- context '<' do
+ describe '<' do
it { expect(@v0_0_1).to be < @v0_1_0 }
it { expect(@v0_1_0).to be < @v1_0_0 }
it { expect(@v1_0_0).to be < @v1_0_1 }
@@ -34,29 +34,29 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(@v1_1_0).to be < @v2_0_0 }
end
- context '<=' do
+ describe '<=' do
it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) }
it { expect(@v0_0_1).to be <= @v0_1_0 }
end
- context '==' do
+ describe '==' do
it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) }
it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) }
it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) }
end
- context '!=' do
+ describe '!=' do
it { expect(@v0_0_1).not_to eq(@v0_1_0) }
end
- context 'unknown' do
+ describe '.unknown' do
it { expect(@unknown).not_to be @v0_0_1 }
it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end
- context 'parse' do
+ describe '.parse' do
it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) }
it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) }
@@ -66,8 +66,37 @@ RSpec.describe 'Gitlab::VersionInfo' do
it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid }
end
- context 'to_s' do
+ describe '.to_s' do
it { expect(@v1_0_0.to_s).to eq("1.0.0") }
it { expect(@unknown.to_s).to eq("Unknown") }
end
+
+ describe '.hash' do
+ it { expect(Gitlab::VersionInfo.parse("1.0.0").hash).to eq(@v1_0_0.hash) }
+ it { expect(Gitlab::VersionInfo.parse("1.0.0.1").hash).to eq(@v1_0_0.hash) }
+ it { expect(Gitlab::VersionInfo.parse("1.0.1b1").hash).to eq(@v1_0_1.hash) }
+ end
+
+ describe '.eql?' do
+ it { expect(Gitlab::VersionInfo.parse("1.0.0").eql?(@v1_0_0)).to be_truthy }
+ it { expect(Gitlab::VersionInfo.parse("1.0.0.1").eql?(@v1_0_0)).to be_truthy }
+ it { expect(@v1_0_1.eql?(@v1_0_0)).to be_falsey }
+ it { expect(@v1_1_0.eql?(@v1_0_0)).to be_falsey }
+ it { expect(@v1_0_0.eql?(@v1_0_0)).to be_truthy }
+ it { expect([@v1_0_0, @v1_1_0, @v1_0_0].uniq).to eq [@v1_0_0, @v1_1_0] }
+ end
+
+ describe '.same_minor_version?' do
+ it { expect(@v0_1_0.same_minor_version?(@v0_0_1)).to be_falsey }
+ it { expect(@v1_0_1.same_minor_version?(@v1_0_0)).to be_truthy }
+ it { expect(@v1_0_0.same_minor_version?(@v1_0_1)).to be_truthy }
+ it { expect(@v1_1_0.same_minor_version?(@v1_0_0)).to be_falsey }
+ it { expect(@v2_0_0.same_minor_version?(@v1_0_0)).to be_falsey }
+ end
+
+ describe '.without_patch' do
+ it { expect(@v0_1_0.without_patch).to eq(@v0_1_0) }
+ it { expect(@v1_0_0.without_patch).to eq(@v1_0_0) }
+ it { expect(@v1_0_1.without_patch).to eq(@v1_0_0) }
+ end
end
diff --git a/spec/lib/gitlab/x509/certificate_spec.rb b/spec/lib/gitlab/x509/certificate_spec.rb
index 2dc30cc871d..d919b99de2a 100644
--- a/spec/lib/gitlab/x509/certificate_spec.rb
+++ b/spec/lib/gitlab/x509/certificate_spec.rb
@@ -116,9 +116,69 @@ RSpec.describe Gitlab::X509::Certificate do
end
end
+ describe '.default_cert_dir' do
+ before do
+ described_class.reset_default_cert_paths
+ end
+
+ after(:context) do
+ described_class.reset_default_cert_paths
+ end
+
+ context 'when SSL_CERT_DIR env variable is not set' do
+ before do
+ stub_env('SSL_CERT_DIR', nil)
+ end
+
+ it 'returns default directory from OpenSSL' do
+ expect(described_class.default_cert_dir).to eq(OpenSSL::X509::DEFAULT_CERT_DIR)
+ end
+ end
+
+ context 'when SSL_CERT_DIR env variable is set' do
+ before do
+ stub_env('SSL_CERT_DIR', '/tmp/foo/certs')
+ end
+
+ it 'returns specified directory' do
+ expect(described_class.default_cert_dir).to eq('/tmp/foo/certs')
+ end
+ end
+ end
+
+ describe '.default_cert_file' do
+ before do
+ described_class.reset_default_cert_paths
+ end
+
+ after(:context) do
+ described_class.reset_default_cert_paths
+ end
+
+ context 'when SSL_CERT_FILE env variable is not set' do
+ before do
+ stub_env('SSL_CERT_FILE', nil)
+ end
+
+ it 'returns default file from OpenSSL' do
+ expect(described_class.default_cert_file).to eq(OpenSSL::X509::DEFAULT_CERT_FILE)
+ end
+ end
+
+ context 'when SSL_CERT_FILE env variable is set' do
+ before do
+ stub_env('SSL_CERT_FILE', '/tmp/foo/cert.pem')
+ end
+
+ it 'returns specified file' do
+ expect(described_class.default_cert_file).to eq('/tmp/foo/cert.pem')
+ end
+ end
+ end
+
describe '.ca_certs_paths' do
it 'returns all files specified by OpenSSL defaults' do
- cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"]
+ cert_paths = Dir["#{described_class.default_cert_dir}/*"]
expect(described_class.ca_certs_paths).to match_array(cert_paths + [sample_cert])
end
diff --git a/spec/lib/gitlab/x509/signature_spec.rb b/spec/lib/gitlab/x509/signature_spec.rb
index 0e34d5393d6..5626e49bfe1 100644
--- a/spec/lib/gitlab/x509/signature_spec.rb
+++ b/spec/lib/gitlab/x509/signature_spec.rb
@@ -107,7 +107,7 @@ RSpec.describe Gitlab::X509::Signature do
f.print certificate.to_pem
end
- stub_const("OpenSSL::X509::DEFAULT_CERT_FILE", file_path)
+ allow(Gitlab::X509::Certificate).to receive(:default_cert_file).and_return(file_path)
allow(OpenSSL::X509::Store).to receive(:new).and_return(store)
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 47fd1622306..1d58a31bd6e 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -788,20 +788,6 @@ RSpec.describe 'project routing' do
it 'to #test' do
expect(put('/gitlab/gitlabhq/-/settings/integrations/acme/test')).to route_to('projects/settings/integrations#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'acme')
end
-
- context 'legacy routes' do
- it 'to #edit' do
- expect(get('/gitlab/gitlabhq/-/integrations/acme/edit')).to route_to('projects/settings/integrations#edit', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'acme')
- end
-
- it 'to #update' do
- expect(put('/gitlab/gitlabhq/-/integrations/acme')).to route_to('projects/settings/integrations#update', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'acme')
- end
-
- it 'to #test' do
- expect(put('/gitlab/gitlabhq/-/integrations/acme/test')).to route_to('projects/settings/integrations#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'acme')
- end
- end
end
describe Projects::Settings::IntegrationHookLogsController do
@@ -812,16 +798,6 @@ RSpec.describe 'project routing' do
it 'to #retry' do
expect(post('/gitlab/gitlabhq/-/settings/integrations/acme/hook_logs/log/retry')).to route_to('projects/settings/integration_hook_logs#retry', namespace_id: 'gitlab', project_id: 'gitlabhq', integration_id: 'acme', id: 'log')
end
-
- context 'legacy routes' do
- it 'to #show' do
- expect(get('/gitlab/gitlabhq/-/integrations/acme/hook_logs/log')).to route_to('projects/settings/integration_hook_logs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', integration_id: 'acme', id: 'log')
- end
-
- it 'to #retry' do
- expect(post('/gitlab/gitlabhq/-/integrations/acme/hook_logs/log/retry')).to route_to('projects/settings/integration_hook_logs#retry', namespace_id: 'gitlab', project_id: 'gitlabhq', integration_id: 'acme', id: 'log')
- end
- end
end
describe Projects::TemplatesController, 'routing' do