diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-14 03:14:57 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-14 03:14:57 +0300 |
commit | 1d54f384d509d9581730e24a64561e94132e41c1 (patch) | |
tree | b53346e16f2129de0872f9e1cc28bb468fffee04 | |
parent | f163fc8ce6d7661ccf0ff9aa4561f6e5a708b71b (diff) |
Add latest changes from gitlab-org/gitlab@master
116 files changed, 454 insertions, 3674 deletions
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index a7541a613f0..4fec223e66d 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -57,11 +57,12 @@ update-qa-cache: - install_gitlab_gem - tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR} script: + - tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE} + - '[ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`"' + - 'echo "QA_TESTS: $QA_TESTS"' + - exit_code=0 && tooling/bin/qa/package_and_qa_check ${CHANGES_DIFFS_DIR} || exit_code=$? + - echo $exit_code - | - tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE} - [ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`" - echo "QA_TESTS: $QA_TESTS" - tooling/bin/qa/package_and_qa_check ${CHANGES_DIFFS_DIR} && exit_code=$? if [ $exit_code -eq 0 ]; then ./scripts/trigger-build omnibus elif [ $exit_code -eq 1 ]; then diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue index c22f532d7ac..edc6573a489 100644 --- a/app/assets/javascripts/header_search/components/app.vue +++ b/app/assets/javascripts/header_search/components/app.vue @@ -140,6 +140,7 @@ export default { class="header-search gl-relative" > <gl-search-box-by-type + id="search" v-model="searchText" role="searchbox" class="gl-z-index-1" diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js index 74d99d02fc5..177e3d6f9cc 100644 --- a/app/assets/javascripts/integrations/constants.js +++ b/app/assets/javascripts/integrations/constants.js @@ -1,6 +1,5 @@ import { s__, __ } from '~/locale'; -export const TEST_INTEGRATION_EVENT = 'testIntegration'; export const SAVE_INTEGRATION_EVENT = 'saveIntegration'; export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm'; diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue index a30c84bd4d2..60a6d13ce69 100644 --- a/app/assets/javascripts/integrations/edit/components/integration_form.vue +++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue @@ -1,14 +1,18 @@ <script> import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; import { mapState, mapActions, mapGetters } from 'vuex'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { - TEST_INTEGRATION_EVENT, SAVE_INTEGRATION_EVENT, + VALIDATE_INTEGRATION_FORM_EVENT, + I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE, + I18N_DEFAULT_ERROR_MESSAGE, + I18N_SUCCESSFUL_CONNECTION_MESSAGE, integrationLevels, } from '~/integrations/constants'; import eventHub from '../event_hub'; - +import { testIntegrationSettings } from '../api'; import ActiveCheckbox from './active_checkbox.vue'; import ConfirmationModal from './confirmation_modal.vue'; import DynamicField from './dynamic_field.vue'; @@ -50,18 +54,12 @@ export default { data() { return { integrationActive: false, + testingLoading: false, }; }, computed: { ...mapGetters(['currentKey', 'propsSource', 'isDisabled']), - ...mapState([ - 'defaultState', - 'customState', - 'override', - 'isSaving', - 'isTesting', - 'isResetting', - ]), + ...mapState(['defaultState', 'customState', 'override', 'isSaving', 'isResetting']), isEditable() { return this.propsSource.editable; }, @@ -74,9 +72,18 @@ export default { this.customState.integrationLevel === integrationLevels.GROUP ); }, - showReset() { + showResetButton() { return this.isInstanceOrGroupLevel && this.propsSource.resetPath; }, + showTestButton() { + return this.propsSource.canTest; + }, + disableSaveButton() { + return Boolean(this.isResetting || this.testingLoading); + }, + disableResetButton() { + return Boolean(this.isSaving || this.testingLoading); + }, }, mounted() { // this form element is defined in Haml @@ -86,7 +93,6 @@ export default { ...mapActions([ 'setOverride', 'setIsSaving', - 'setIsTesting', 'setIsResetting', 'fetchResetIntegration', 'requestJiraIssueTypes', @@ -98,17 +104,39 @@ export default { eventHub.$emit(SAVE_INTEGRATION_EVENT, formValid); }, onTestClick() { - this.setIsTesting(true); + this.testingLoading = true; - const formValid = this.form.checkValidity(); - eventHub.$emit(TEST_INTEGRATION_EVENT, formValid); + if (!this.form.checkValidity()) { + eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); + return; + } + + testIntegrationSettings(this.propsSource.testPath, this.getFormData()) + .then(({ data: { error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } }) => { + if (error) { + eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); + this.$toast.show(message); + return; + } + + this.$toast.show(I18N_SUCCESSFUL_CONNECTION_MESSAGE); + }) + .catch((error) => { + this.$toast.show(I18N_DEFAULT_ERROR_MESSAGE); + Sentry.captureException(error); + }) + .finally(() => { + this.testingLoading = false; + }); }, onResetClick() { this.fetchResetIntegration(); }, onRequestJiraIssueTypes() { - const formData = new FormData(this.form); - this.requestJiraIssueTypes(formData); + this.requestJiraIssueTypes(this.getFormData()); + }, + getFormData() { + return new FormData(this.form); }, onToggleIntegrationState(integrationActive) { this.integrationActive = integrationActive; @@ -183,7 +211,7 @@ export default { category="primary" variant="confirm" :loading="isSaving" - :disabled="isDisabled" + :disabled="disableSaveButton" data-qa-selector="save_changes_button" > {{ __('Save changes') }} @@ -196,7 +224,7 @@ export default { variant="confirm" type="submit" :loading="isSaving" - :disabled="isDisabled" + :disabled="disableSaveButton" data-testid="save-button" data-qa-selector="save_changes_button" @click.prevent="onSaveClick" @@ -205,25 +233,24 @@ export default { </gl-button> <gl-button - v-if="propsSource.canTest" + v-if="showTestButton" category="secondary" variant="confirm" - :loading="isTesting" + :loading="testingLoading" :disabled="isDisabled" - :href="propsSource.testPath" data-testid="test-button" @click.prevent="onTestClick" > {{ __('Test settings') }} </gl-button> - <template v-if="showReset"> + <template v-if="showResetButton"> <gl-button v-gl-modal.confirmResetIntegration category="secondary" variant="confirm" :loading="isResetting" - :disabled="isDisabled" + :disabled="disableResetButton" data-testid="reset-button" > {{ __('Reset') }} diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js index b81ae1b1cb6..6c1bfaa7858 100644 --- a/app/assets/javascripts/integrations/edit/store/actions.js +++ b/app/assets/javascripts/integrations/edit/store/actions.js @@ -11,7 +11,6 @@ import * as types from './mutation_types'; export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override); export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING, isSaving); -export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting); export const setIsResetting = ({ commit }, isResetting) => commit(types.SET_IS_RESETTING, isResetting); diff --git a/app/assets/javascripts/integrations/edit/store/getters.js b/app/assets/javascripts/integrations/edit/store/getters.js index 39e14de2d0d..10d370de856 100644 --- a/app/assets/javascripts/integrations/edit/store/getters.js +++ b/app/assets/javascripts/integrations/edit/store/getters.js @@ -1,6 +1,6 @@ export const isInheriting = (state) => (state.defaultState === null ? false : !state.override); -export const isDisabled = (state) => state.isSaving || state.isTesting || state.isResetting; +export const isDisabled = (state) => state.isSaving || state.isResetting; export const propsSource = (state, getters) => getters.isInheriting ? state.defaultState : state.customState; diff --git a/app/assets/javascripts/integrations/edit/store/mutation_types.js b/app/assets/javascripts/integrations/edit/store/mutation_types.js index c681056a515..dd4a79f6fe0 100644 --- a/app/assets/javascripts/integrations/edit/store/mutation_types.js +++ b/app/assets/javascripts/integrations/edit/store/mutation_types.js @@ -1,6 +1,5 @@ export const SET_OVERRIDE = 'SET_OVERRIDE'; export const SET_IS_SAVING = 'SET_IS_SAVING'; -export const SET_IS_TESTING = 'SET_IS_TESTING'; export const SET_IS_RESETTING = 'SET_IS_RESETTING'; export const SET_IS_LOADING_JIRA_ISSUE_TYPES = 'SET_IS_LOADING_JIRA_ISSUE_TYPES'; diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js index 279df1b9266..f76081e62f0 100644 --- a/app/assets/javascripts/integrations/edit/store/mutations.js +++ b/app/assets/javascripts/integrations/edit/store/mutations.js @@ -7,9 +7,6 @@ export default { [types.SET_IS_SAVING](state, isSaving) { state.isSaving = isSaving; }, - [types.SET_IS_TESTING](state, isTesting) { - state.isTesting = isTesting; - }, [types.SET_IS_RESETTING](state, isResetting) { state.isResetting = isResetting; }, diff --git a/app/assets/javascripts/integrations/edit/store/state.js b/app/assets/javascripts/integrations/edit/store/state.js index 1c0b274e4ef..3d40d1b90d5 100644 --- a/app/assets/javascripts/integrations/edit/store/state.js +++ b/app/assets/javascripts/integrations/edit/store/state.js @@ -6,7 +6,6 @@ export default ({ defaultState = null, customState = {} } = {}) => { defaultState, customState, isSaving: false, - isTesting: false, isResetting: false, isLoadingJiraIssueTypes: false, loadingJiraIssueTypesErrorMessage: '', diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index d3c227df1ee..193bc1c4e9b 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -1,15 +1,7 @@ import { delay } from 'lodash'; -import toast from '~/vue_shared/plugins/global_toast'; import initForm from './edit'; import eventHub from './edit/event_hub'; -import { - TEST_INTEGRATION_EVENT, - SAVE_INTEGRATION_EVENT, - VALIDATE_INTEGRATION_FORM_EVENT, - I18N_DEFAULT_ERROR_MESSAGE, - I18N_SUCCESSFUL_CONNECTION_MESSAGE, -} from './constants'; -import { testIntegrationSettings } from './edit/api'; +import { SAVE_INTEGRATION_EVENT, VALIDATE_INTEGRATION_FORM_EVENT } from './constants'; export default class IntegrationSettingsForm { constructor(formSelector) { @@ -29,9 +21,6 @@ export default class IntegrationSettingsForm { document.querySelector('.js-vue-default-integration-settings'), this.formSelector, ); - eventHub.$on(TEST_INTEGRATION_EVENT, (formValid) => { - this.testIntegration(formValid); - }); eventHub.$on(SAVE_INTEGRATION_EVENT, (formValid) => { this.saveIntegration(formValid); }); @@ -53,47 +42,4 @@ export default class IntegrationSettingsForm { this.vue.$store.dispatch('setIsSaving', false); } } - - testIntegration(formValid) { - // Service was marked active so now we check; - // 1) If form contents are valid - // 2) If this service can be tested - // If both conditions are true, we override form submission - // and test the service using provided configuration. - if (formValid) { - this.testSettings(new FormData(this.$form)); - } else { - eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT); - this.vue.$store.dispatch('setIsTesting', false); - } - } - - /** - * Get a list of Jira issue types for the currently configured project - * - * @param {string} formData - URL encoded string containing the form data - * - * @return {Promise} - */ - - /** - * Test Integration config - */ - testSettings(formData) { - return testIntegrationSettings(this.testEndPoint, formData) - .then(({ data }) => { - if (data.error) { - toast(`${data.message} ${data.service_response}`); - } else { - this.vue.$store.dispatch('receiveJiraIssueTypesSuccess', data.issuetypes); - toast(I18N_SUCCESSFUL_CONNECTION_MESSAGE); - } - }) - .catch(() => { - toast(I18N_DEFAULT_ERROR_MESSAGE); - }) - .finally(() => { - this.vue.$store.dispatch('setIsTesting', false); - }); - } } diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 0d307d7744b..99e8058b57d 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -14,7 +14,6 @@ import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { initRails } from '~/lib/utils/rails_ujs'; import * as popovers from '~/popovers'; import * as tooltips from '~/tooltips'; -import { initHeaderSearchApp } from '~/header_search'; import initAlertHandler from './alert_handler'; import { addDismissFlashClickListener } from './flash'; import initTodoToggle from './header'; @@ -100,24 +99,29 @@ function deferredInitialisation() { initFeatureHighlight(); initCopyCodeButton(); - if (gon.features?.newHeaderSearch) { - initHeaderSearchApp(); - } else { - const search = document.querySelector('#search'); - if (search) { - search.addEventListener( - 'focus', - () => { + const search = document.querySelector('#search'); + if (search) { + search.addEventListener( + 'focus', + () => { + if (gon.features?.newHeaderSearch) { + import(/* webpackChunkName: 'globalSearch' */ '~/header_search') + .then(async ({ initHeaderSearchApp }) => { + await initHeaderSearchApp(); + document.querySelector('#search').focus(); + }) + .catch(() => {}); + } else { import(/* webpackChunkName: 'globalSearch' */ './search_autocomplete') .then(({ default: initSearchAutocomplete }) => { const searchDropdown = initSearchAutocomplete(); searchDropdown.onSearchInputFocus(); }) .catch(() => {}); - }, - { once: true }, - ); - } + } + }, + { once: true }, + ); } addSelectOnFocusBehaviour('.js-select-on-focus'); diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue index f07fb9d926a..e3aa29d5f89 100644 --- a/app/assets/javascripts/snippets/components/edit.vue +++ b/app/assets/javascripts/snippets/components/edit.vue @@ -230,7 +230,7 @@ export default { <gl-button category="primary" type="submit" - variant="success" + variant="confirm" :disabled="updatePrevented" data-qa-selector="submit_button" data-testid="snippet-submit-btn" diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue index a5c98a7ad90..9b24c8afe37 100644 --- a/app/assets/javascripts/snippets/components/snippet_header.vue +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -113,7 +113,7 @@ export default { href: this.snippet.project ? joinPaths(this.snippet.project.webUrl, '-/snippets/new') : joinPaths('/', gon.relative_url_root, '/-/snippets/new'), - variant: 'success', + variant: 'confirm', category: 'secondary', }, { diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss index 03dcbd2b8f9..345c180d164 100644 --- a/app/assets/stylesheets/framework/contextual_sidebar.scss +++ b/app/assets/stylesheets/framework/contextual_sidebar.scss @@ -376,6 +376,18 @@ } } } + + li > a.gl-link { + // undo gl-link text items for things in the sidebar - including sub menus + // defined in https://gitlab.com/gitlab-org/gitlab-ui/-/blob/5431e0ca5149d4e02e3d5d617d194ac9609bb82d/src/components/base/link/link.scss + @include gl-text-body; + + &:active, + &:focus, + &:focus:active { + @include gl-text-decoration-none; + } + } } .sidebar-sub-level-items { diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss index 9914e573247..63305bc592d 100644 --- a/app/assets/stylesheets/startup/startup-dark.scss +++ b/app/assets/stylesheets/startup/startup-dark.scss @@ -407,6 +407,34 @@ h1 { .gl-form-input.form-control::placeholder { color: #868686; } +.gl-icon { + fill: currentColor; +} +.gl-icon.s12 { + width: 12px; + height: 12px; +} +.gl-icon.s16 { + width: 16px; + height: 16px; +} +.gl-icon.s32 { + width: 32px; + height: 32px; +} +.gl-link { + font-size: 0.875rem; + color: #428fdc; +} +.gl-link:active { + color: #9dc7f1; +} +.gl-link:active { + text-decoration: underline; + box-shadow: 0 0 0 1px rgba(51, 51, 51, 0.4), + 0 0 0 4px rgba(66, 143, 220, 0.48); + outline: none; +} .gl-button { display: inline-flex; } @@ -439,6 +467,29 @@ h1 { outline: none; background-color: #404040; } +.gl-button.gl-button.btn-default:active .gl-icon, +.gl-button.gl-button.btn-default.active .gl-icon { + color: #fafafa; +} +.gl-button.gl-button.btn-default .gl-icon { + color: #999; +} +.gl-search-box-by-type-search-icon { + margin: 0.5rem; + color: #999; + width: 1rem; + position: absolute; +} +.gl-search-box-by-type { + display: flex; + position: relative; +} +.gl-search-box-by-type-input, +.gl-search-box-by-type-input.gl-form-input { + height: 2rem; + padding-right: 2rem; + padding-left: 1.75rem; +} body, .form-control, .search form { @@ -1334,6 +1385,12 @@ input { font-weight: 400; color: #9dc7f1; } +.sidebar-top-level-items li > a.gl-link { + color: #fafafa; +} +.sidebar-top-level-items li > a.gl-link:active { + text-decoration: none; +} .sidebar-sub-level-items { padding-top: 0; padding-bottom: 0; diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss index e6d7185e29c..a57202515ad 100644 --- a/app/assets/stylesheets/startup/startup-general.scss +++ b/app/assets/stylesheets/startup/startup-general.scss @@ -388,6 +388,34 @@ h1 { .gl-form-input.form-control::placeholder { color: #868686; } +.gl-icon { + fill: currentColor; +} +.gl-icon.s12 { + width: 12px; + height: 12px; +} +.gl-icon.s16 { + width: 16px; + height: 16px; +} +.gl-icon.s32 { + width: 32px; + height: 32px; +} +.gl-link { + font-size: 0.875rem; + color: #1f75cb; +} +.gl-link:active { + color: #0b5cad; +} +.gl-link:active { + text-decoration: underline; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.4), + 0 0 0 4px rgba(31, 117, 203, 0.48); + outline: none; +} .gl-button { display: inline-flex; } @@ -420,6 +448,29 @@ h1 { outline: none; background-color: #dbdbdb; } +.gl-button.gl-button.btn-default:active .gl-icon, +.gl-button.gl-button.btn-default.active .gl-icon { + color: #303030; +} +.gl-button.gl-button.btn-default .gl-icon { + color: #666; +} +.gl-search-box-by-type-search-icon { + margin: 0.5rem; + color: #666; + width: 1rem; + position: absolute; +} +.gl-search-box-by-type { + display: flex; + position: relative; +} +.gl-search-box-by-type-input, +.gl-search-box-by-type-input.gl-form-input { + height: 2rem; + padding-right: 2rem; + padding-left: 1.75rem; +} body, .form-control, .search form { @@ -1315,6 +1366,12 @@ input { font-weight: 400; color: #0b5cad; } +.sidebar-top-level-items li > a.gl-link { + color: #303030; +} +.sidebar-top-level-items li > a.gl-link:active { + text-decoration: none; +} .sidebar-sub-level-items { padding-top: 0; padding-bottom: 0; diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index d2cc1f8640f..a8110d15d8e 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -291,18 +291,6 @@ $gl-line-height-42: px-to-rem(42px); @include gl-focus($gl-border-size-1, $gray-900, true); } -// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2476 -.gl-md-max-w-50p { - @include gl-media-breakpoint-up(md) { - max-width: 50%; - } -} - -// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2497 -.gl-z-index-200 { - z-index: 200; -} - // Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1637 .gl-lg-w-25p { @include gl-media-breakpoint-up(lg) { diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 2daf098b94a..1d1d9b6bec7 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -56,6 +56,8 @@ class IssuableBaseService < ::BaseProjectService # confidential attribute is a special type of metadata and needs to be allowed to be set # by non-members on issues in public projects so that security issues can be reported as confidential. params.delete(:confidential) unless can?(current_user, :set_confidentiality, issuable) + params.delete(:add_contacts) unless can?(current_user, :set_issue_crm_contacts, issuable) + params.delete(:remove_contacts) unless can?(current_user, :set_issue_crm_contacts, issuable) filter_assignees(issuable) filter_milestone @@ -206,6 +208,9 @@ class IssuableBaseService < ::BaseProjectService params[:assignee_ids] = process_assignee_ids(params, extra_assignee_ids: issuable.assignee_ids.to_a) end + params.delete(:remove_contacts) + add_crm_contact_emails = params.delete(:add_contacts) + issuable.assign_attributes(allowed_create_params(params)) before_create(issuable) @@ -219,6 +224,7 @@ class IssuableBaseService < ::BaseProjectService handle_changes(issuable, { params: params }) after_create(issuable) + set_crm_contacts(issuable, add_crm_contact_emails) execute_hooks(issuable) users_to_invalidate = issuable.allows_reviewers? ? issuable.assignees | issuable.reviewers : issuable.assignees @@ -229,6 +235,12 @@ class IssuableBaseService < ::BaseProjectService issuable end + def set_crm_contacts(issuable, add_crm_contact_emails, remove_crm_contact_emails = []) + return unless add_crm_contact_emails.present? || remove_crm_contact_emails.present? + + ::Issues::SetCrmContactsService.new(project: project, current_user: current_user, params: { add_emails: add_crm_contact_emails, remove_emails: remove_crm_contact_emails }).execute(issuable) + end + def before_create(issuable) # To be overridden by subclasses end @@ -254,6 +266,7 @@ class IssuableBaseService < ::BaseProjectService assign_requested_labels(issuable) assign_requested_assignees(issuable) + assign_requested_crm_contacts(issuable) if issuable.changed? || params.present? issuable.assign_attributes(allowed_update_params(params)) @@ -414,6 +427,12 @@ class IssuableBaseService < ::BaseProjectService issuable.touch end + def assign_requested_crm_contacts(issuable) + add_crm_contact_emails = params.delete(:add_contacts) + remove_crm_contact_emails = params.delete(:remove_contacts) + set_crm_contacts(issuable, add_crm_contact_emails, remove_crm_contact_emails) + end + def assign_requested_assignees(issuable) return if issuable.is_a?(Epic) diff --git a/app/services/issues/set_crm_contacts_service.rb b/app/services/issues/set_crm_contacts_service.rb index f0d95934ea4..c435ab81b4d 100644 --- a/app/services/issues/set_crm_contacts_service.rb +++ b/app/services/issues/set_crm_contacts_service.rb @@ -12,7 +12,7 @@ module Issues return error_no_permissions unless allowed? return error_invalid_params unless valid_params? - @existing_ids = issue.issue_customer_relations_contacts.map(&:contact_id) + @existing_ids = issue.customer_relations_contact_ids determine_changes if params[:replace_ids].present? return error_too_many if too_many? @@ -24,6 +24,7 @@ module Issues if issue.valid? GraphqlTriggers.issue_crm_contacts_updated(issue) + issue.touch ServiceResponse.success(payload: issue) else # The default error isn't very helpful: "Issue customer relations contacts is invalid" diff --git a/app/views/groups/_invite_members_side_nav_link.html.haml b/app/views/groups/_invite_members_side_nav_link.html.haml index bccfa9897da..3046669b53b 100644 --- a/app/views/groups/_invite_members_side_nav_link.html.haml +++ b/app/views/groups/_invite_members_side_nav_link.html.haml @@ -1,5 +1,4 @@ .js-invite-members-trigger{ data: { trigger_source: 'group-side-nav', - classes: 'gl-text-decoration-none! gl-shadow-none! gl-text-body!', icon: 'users', display_text: title, trigger_element: 'side-nav'} } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 01dfecb585a..a7c08bafba6 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -36,7 +36,9 @@ 'issues-path' => issues_dashboard_path, 'mr-path' => merge_requests_dashboard_path, 'autocomplete-path' => search_autocomplete_path } } - %input{ type: "text", placeholder: _('Search or jump to...'), class: 'form-control gl-form-input' } + .gl-search-box-by-type + = sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon') + %input{ type: "text", placeholder: _('Search or jump to...'), class: 'form-control gl-form-input gl-search-box-by-type-input', id: 'search', autocomplete: 'off' } - else = render 'layouts/search' %li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' } diff --git a/app/views/projects/_invite_members_side_nav_link.html.haml b/app/views/projects/_invite_members_side_nav_link.html.haml index ea6174d19f0..fae681b1a71 100644 --- a/app/views/projects/_invite_members_side_nav_link.html.haml +++ b/app/views/projects/_invite_members_side_nav_link.html.haml @@ -1,5 +1,4 @@ .js-invite-members-trigger{ data: { trigger_source: 'project-side-nav', - classes: 'gl-text-decoration-none! gl-shadow-none! gl-text-body!', icon: 'users', display_text: title, trigger_element: 'side-nav'} } diff --git a/app/views/shared/nav/_scope_menu.html.haml b/app/views/shared/nav/_scope_menu.html.haml index 1a7089fb570..4e570086bf8 100644 --- a/app/views/shared/nav/_scope_menu.html.haml +++ b/app/views/shared/nav/_scope_menu.html.haml @@ -1,5 +1,5 @@ = nav_link(**scope_menu.active_routes, html_options: scope_menu.nav_link_html_options) do - = link_to scope_menu.link, **scope_menu.container_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: scope_qa_menu_item(scope_menu.container) } do + = link_to scope_menu.link, **scope_menu.link_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: scope_qa_menu_item(scope_menu.container) } do %span{ class: scope_avatar_classes(scope_menu.container) } = source_icon(scope_menu.container, alt: scope_menu.title, class: ['avatar', 'avatar-tile', 's32'], width: 32, height: 32) %span.sidebar-context-title diff --git a/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml b/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml index 953f7a8ae60..d0ae5e99707 100644 --- a/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml +++ b/app/views/shared/nav/_sidebar_hidden_menu_item.html.haml @@ -1,3 +1,3 @@ %li.hidden - = link_to sidebar_hidden_menu_item.link, **sidebar_hidden_menu_item.container_html_options do + = link_to sidebar_hidden_menu_item.link, **sidebar_hidden_menu_item.link_html_options do = sidebar_hidden_menu_item.title diff --git a/app/views/shared/nav/_sidebar_menu.html.haml b/app/views/shared/nav/_sidebar_menu.html.haml index 3f71368aff3..4c4ceb9ea70 100644 --- a/app/views/shared/nav/_sidebar_menu.html.haml +++ b/app/views/shared/nav/_sidebar_menu.html.haml @@ -2,7 +2,7 @@ - if sidebar_menu.menu_with_partial? = render_if_exists sidebar_menu.menu_partial, **sidebar_menu.menu_partial_options - else - = link_to sidebar_menu.link, **sidebar_menu.container_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: sidebar_menu.title } do + = link_to sidebar_menu.link, **sidebar_menu.link_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: sidebar_menu.title } do - if sidebar_menu.icon_or_image? %span.nav-icon-container - if sidebar_menu.image_path diff --git a/app/views/shared/nav/_sidebar_menu_item.html.haml b/app/views/shared/nav/_sidebar_menu_item.html.haml index 674ce593ee2..5452cd486da 100644 --- a/app/views/shared/nav/_sidebar_menu_item.html.haml +++ b/app/views/shared/nav/_sidebar_menu_item.html.haml @@ -1,5 +1,5 @@ = nav_link(**sidebar_menu_item.active_routes, html_options: sidebar_menu_item.nav_link_html_options) do - = link_to sidebar_menu_item.link, **sidebar_menu_item.container_html_options, data: { qa_selector: 'sidebar_menu_item_link', qa_menu_item: sidebar_menu_item.title } do + = link_to sidebar_menu_item.link, **sidebar_menu_item.link_html_options, data: { qa_selector: 'sidebar_menu_item_link', qa_menu_item: sidebar_menu_item.title } do %span = sidebar_menu_item.title - if sidebar_menu_item.sprite_icon diff --git a/config/feature_flags/ops/database_async_index_creation.yml b/config/feature_flags/ops/database_async_index_creation.yml new file mode 100644 index 00000000000..60d491b6073 --- /dev/null +++ b/config/feature_flags/ops/database_async_index_creation.yml @@ -0,0 +1,8 @@ +--- +name: database_async_index_creation +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66478 +rollout_issue_url: +milestone: '14.2' +type: ops +group: group::database +default_enabled: false diff --git a/doc/administration/gitaly/recovery.md b/doc/administration/gitaly/recovery.md index 31316693917..e1b9a73908d 100644 --- a/doc/administration/gitaly/recovery.md +++ b/doc/administration/gitaly/recovery.md @@ -371,12 +371,13 @@ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.t ### Manually track repositories -> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5658) in GitLab 14.4. +> - [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5658) in GitLab 14.4. +> - [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/5789) in GitLab 14.6, support for immediate replication. The `track-repository` Praefect sub-command adds repositories on disk to the Praefect database to be tracked. ```shell -sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml track-repository -virtual-storage <virtual-storage> -repository <repository> +sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.toml track-repository -virtual-storage <virtual-storage> -repository <repository> -replicate-immediately ``` - `-virtual-storage` is the virtual storage the repository is located in. Virtual storages are configured in `/etc/gitlab/gitlab.rb` under `praefect['virtual_storages]` and looks like the following: @@ -403,6 +404,8 @@ sudo /opt/gitlab/embedded/bin/praefect -config /var/opt/gitlab/praefect/config.t - `-authoritative-storage` is the storage we want Praefect to treat as the primary. Required if [per-repository replication](praefect.md#configure-replication-factor) is set as the replication strategy. +- `-replicate-immediately`, available in GitLab 14.6 and later, causes the command to replicate the repository to its secondaries immediately. + Otherwise, replication jobs are scheduled for execution in the database and are picked up by a Praefect background process. The command outputs: diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md index dd4558b4a3e..2b7d90be631 100644 --- a/doc/user/group/saml_sso/scim_setup.md +++ b/doc/user/group/saml_sso/scim_setup.md @@ -303,3 +303,12 @@ As a workaround, try an alternate mapping: 1. Follow the Azure mapping instructions from above. 1. Delete the `name.formatted` target attribute entry. 1. Change the `displayName` source attribute to have `name.formatted` target attribute. + +#### Failed to match an entry in the source and target systems Group 'Group-Name' + +Group provisioning in Azure can fail with the `Failed to match an entry in the source and target systems Group 'Group-Name'` error message, +and the error response can include a HTML result of the GitLab URL `https://gitlab.com/users/sign_in`. + +This error is harmless and occurs because Group provisioning was turned on but GitLab SCIM integration does not support it nor require it. To +remove the error, follow the instructions in the Azure configuration guide to disable the option +[`Synchronize Azure Active Directory Groups to AppName`](#azure-configuration-steps). diff --git a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb deleted file mode 100644 index 5b9ee8a0ee2..00000000000 --- a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class AddMergeRequestDiffCommitsCount - class MergeRequestDiff < ActiveRecord::Base - self.table_name = 'merge_request_diffs' - end - - def perform(start_id, stop_id) - Gitlab::AppLogger.info("Setting commits_count for merge request diffs: #{start_id} - #{stop_id}") - - update = ' - commits_count = ( - SELECT count(*) - FROM merge_request_diff_commits - WHERE merge_request_diffs.id = merge_request_diff_commits.merge_request_diff_id - )'.squish - - MergeRequestDiff.where(id: start_id..stop_id).where(commits_count: nil).update_all(update) - end - end - end -end diff --git a/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule.rb b/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule.rb deleted file mode 100644 index 2148e96f6b4..00000000000 --- a/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Compare all current rules to project rules - class AddModifiedToApprovalMergeRequestRule - # Stubbed class to access the Group table - class Group < ActiveRecord::Base - self.table_name = 'namespaces' - self.inheritance_column = :_type_disabled - end - - # Stubbed class to access the ApprovalMergeRequestRule table - class ApprovalMergeRequestRule < ActiveRecord::Base - self.table_name = 'approval_merge_request_rules' - - has_one :approval_merge_request_rule_source, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalMergeRequestRuleSource' - has_one :approval_project_rule, through: :approval_merge_request_rule_source - has_and_belongs_to_many :groups, - class_name: 'AddModifiedToApprovalMergeRequestRule::Group', join_table: "#{self.table_name}_groups" - end - - # Stubbed class to access the ApprovalProjectRule table - class ApprovalProjectRule < ActiveRecord::Base - self.table_name = 'approval_project_rules' - - has_many :approval_merge_request_rule_sources, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalMergeRequestRuleSource' - has_and_belongs_to_many :groups, - class_name: 'AddModifiedToApprovalMergeRequestRule::Group', join_table: "#{self.table_name}_groups" - end - - # Stubbed class to access the ApprovalMergeRequestRuleSource table - class ApprovalMergeRequestRuleSource < ActiveRecord::Base - self.table_name = 'approval_merge_request_rule_sources' - - belongs_to :approval_merge_request_rule, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalMergeRequestRule' - belongs_to :approval_project_rule, class_name: 'AddModifiedToApprovalMergeRequestRule::ApprovalProjectRule' - end - - def perform(start_id, stop_id) - approval_merge_requests_rules = ApprovalMergeRequestRule - .joins(:groups, :approval_merge_request_rule_source) - .where(id: start_id..stop_id) - .pluck( - 'approval_merge_request_rule_sources.id as ars_id', - 'approval_merge_request_rules_groups.id as amrg_id' - ) - - approval_project_rules = ApprovalProjectRule - .joins(:groups, approval_merge_request_rule_sources: :approval_merge_request_rule) - .where(approval_merge_request_rules: { id: start_id..stop_id }) - .pluck( - 'approval_merge_request_rule_sources.id as ars_id', - 'approval_project_rules_groups.id as apg_id' - ) - - different_names_or_approval_sources = ApprovalMergeRequestRule.joins(:approval_project_rule, :approval_merge_request_rule_source) - .where(id: start_id..stop_id) - .where('approval_merge_request_rules.name != approval_project_rules.name OR ' \ - 'approval_merge_request_rules.approvals_required != approval_project_rules.approvals_required') - .pluck('approval_merge_request_rule_sources.id as ars_id') - - intersected_set = approval_merge_requests_rules.to_set ^ approval_project_rules.to_set - source_ids = intersected_set.collect { |rule| rule[0] }.uniq - - rule_sources = ApprovalMergeRequestRuleSource.where(id: source_ids + different_names_or_approval_sources) - changed_merge_request_rules = ApprovalMergeRequestRule.where(id: rule_sources.select(:approval_merge_request_rule_id)) - - changed_merge_request_rules.update_all(modified_from_project_rule: true) - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments.rb b/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments.rb deleted file mode 100644 index 9778f360e87..00000000000 --- a/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Backfill deployment_clusters for a range of deployments - class BackfillDeploymentClustersFromDeployments - def perform(start_id, end_id) - ActiveRecord::Base.connection.execute <<~SQL - INSERT INTO deployment_clusters (deployment_id, cluster_id) - SELECT deployments.id, deployments.cluster_id - FROM deployments - WHERE deployments.cluster_id IS NOT NULL - AND deployments.id BETWEEN #{start_id} AND #{end_id} - ON CONFLICT DO NOTHING - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests.rb b/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests.rb deleted file mode 100644 index 4fd3b81fda3..00000000000 --- a/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # BackfillEnvironmentIdDeploymentMergeRequests deletes duplicates - # from deployment_merge_requests table and backfills environment_id - class BackfillEnvironmentIdDeploymentMergeRequests - def perform(_start_mr_id, _stop_mr_id) - # no-op - - # Background migration removed due to - # https://gitlab.com/gitlab-org/gitlab/-/issues/217191 - end - - def backfill_range(start_mr_id, stop_mr_id) - start_mr_id = Integer(start_mr_id) - stop_mr_id = Integer(stop_mr_id) - - ActiveRecord::Base.connection.execute(<<~SQL) - DELETE FROM deployment_merge_requests - WHERE (deployment_id, merge_request_id) in ( - SELECT t.deployment_id, t.merge_request_id FROM ( - SELECT mrd.merge_request_id, mrd.deployment_id, ROW_NUMBER() OVER w AS rnum - FROM deployment_merge_requests as mrd - INNER JOIN "deployments" ON "deployments"."id" = "mrd"."deployment_id" - WHERE mrd.merge_request_id BETWEEN #{start_mr_id} AND #{stop_mr_id} - WINDOW w AS ( - PARTITION BY merge_request_id, deployments.environment_id - ORDER BY deployments.id - ) - ) t - WHERE t.rnum > 1 - ); - SQL - - ActiveRecord::Base.connection.execute(<<~SQL) - UPDATE deployment_merge_requests - SET environment_id = deployments.environment_id - FROM deployments - WHERE deployments.id = "deployment_merge_requests".deployment_id - AND "deployment_merge_requests".environment_id IS NULL - AND "deployment_merge_requests".merge_request_id BETWEEN #{start_mr_id} AND #{stop_mr_id} - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb b/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb deleted file mode 100644 index 8a58cf9b302..00000000000 --- a/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Backfill merge request cleanup schedules of closed/merged merge requests - # without any corresponding records. - class BackfillMergeRequestCleanupSchedules - # Model used for migration added in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46782. - class MergeRequest < ActiveRecord::Base - include EachBatch - - self.table_name = 'merge_requests' - - def self.eligible - where('merge_requests.state_id IN (2, 3)') - end - end - - def perform(start_id, end_id) - eligible_mrs = MergeRequest.eligible.where(id: start_id..end_id) - scheduled_at_column = "COALESCE(metrics.merged_at, COALESCE(metrics.latest_closed_at, merge_requests.updated_at)) + interval '14 days'" - query = - eligible_mrs - .select("merge_requests.id, #{scheduled_at_column}, NOW(), NOW()") - .joins('LEFT JOIN merge_request_metrics metrics ON metrics.merge_request_id = merge_requests.id') - - result = ActiveRecord::Base.connection.execute <<~SQL - INSERT INTO merge_request_cleanup_schedules (merge_request_id, scheduled_at, created_at, updated_at) - #{query.to_sql} - ON CONFLICT (merge_request_id) DO NOTHING; - SQL - - ::Gitlab::BackgroundMigration::Logger.info( - message: 'Backfilled merge_request_cleanup_schedules records', - count: result.cmd_tuples - ) - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_namespace_settings.rb b/lib/gitlab/background_migration/backfill_namespace_settings.rb deleted file mode 100644 index a391d5f4ebe..00000000000 --- a/lib/gitlab/background_migration/backfill_namespace_settings.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Backfillnamespace_settings for a range of namespaces - class BackfillNamespaceSettings - def perform(start_id, end_id) - ActiveRecord::Base.connection.execute <<~SQL - INSERT INTO namespace_settings (namespace_id, created_at, updated_at) - SELECT namespaces.id, now(), now() - FROM namespaces - WHERE namespaces.id BETWEEN #{start_id} AND #{end_id} - ON CONFLICT (namespace_id) DO NOTHING; - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_project_settings.rb b/lib/gitlab/background_migration/backfill_project_settings.rb deleted file mode 100644 index 7d7f19e1fda..00000000000 --- a/lib/gitlab/background_migration/backfill_project_settings.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Backfill project_settings for a range of projects - class BackfillProjectSettings - def perform(start_id, end_id) - ActiveRecord::Base.connection.execute <<~SQL - INSERT INTO project_settings (project_id, created_at, updated_at) - SELECT projects.id, now(), now() - FROM projects - WHERE projects.id BETWEEN #{start_id} AND #{end_id} - ON CONFLICT (project_id) DO NOTHING; - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/backfill_push_rules_id_in_projects.rb b/lib/gitlab/background_migration/backfill_push_rules_id_in_projects.rb deleted file mode 100644 index 9b9ef70424a..00000000000 --- a/lib/gitlab/background_migration/backfill_push_rules_id_in_projects.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Class that will insert record into project_push_rules - # for each existing push_rule - class BackfillPushRulesIdInProjects - # Temporary AR table for push rules - class ProjectSetting < ActiveRecord::Base - self.table_name = 'project_settings' - end - - def perform(start_id, stop_id) - ProjectSetting.connection.execute(<<~SQL) - UPDATE project_settings ps1 - SET push_rule_id = pr.id - FROM project_settings ps2 - INNER JOIN push_rules pr - ON ps2.project_id = pr.project_id - WHERE pr.is_sample = false - AND pr.id BETWEEN #{start_id} AND #{stop_id} - AND ps1.project_id = ps2.project_id - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids.rb b/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids.rb deleted file mode 100644 index 1a80ccdee92..00000000000 --- a/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This migration updates discussion ids for epics that were promoted from issue so that the discussion id on epics - # is different from discussion id on issue, which was causing problems when replying to epic discussions as it would - # identify the discussion as related to an issue and complaint about missing project_id - class FixPromotedEpicsDiscussionIds - # notes model to iterate through the notes to be updated - class Note < ActiveRecord::Base - self.table_name = 'notes' - self.inheritance_column = :_type_disabled - end - - def perform(discussion_ids) - Note.where(noteable_type: 'Epic') - .where(discussion_id: discussion_ids) - .update_all("discussion_id=MD5(discussion_id)||substring(discussion_id from 1 for 8)") - end - end - end -end diff --git a/lib/gitlab/background_migration/fix_user_namespace_names.rb b/lib/gitlab/background_migration/fix_user_namespace_names.rb deleted file mode 100644 index cd5b4ab103d..00000000000 --- a/lib/gitlab/background_migration/fix_user_namespace_names.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This migration fixes the namespaces.name for all user-namespaces that have names - # that aren't equal to the users name. - # Then it uses the updated names of the namespaces to update the associated routes - # For more info see https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/23272 - class FixUserNamespaceNames - def perform(from_id, to_id) - fix_namespace_names(from_id, to_id) - fix_namespace_route_names(from_id, to_id) - end - - def fix_namespace_names(from_id, to_id) - ActiveRecord::Base.connection.execute <<~UPDATE_NAMESPACES - WITH namespaces_to_update AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} ( - SELECT - namespaces.id, - users.name AS correct_name - FROM - namespaces - INNER JOIN users ON namespaces.owner_id = users.id - WHERE - namespaces.type IS NULL - AND namespaces.id BETWEEN #{from_id} AND #{to_id} - AND namespaces.name != users.name - ) - UPDATE - namespaces - SET - name = correct_name - FROM - namespaces_to_update - WHERE - namespaces.id = namespaces_to_update.id - UPDATE_NAMESPACES - end - - def fix_namespace_route_names(from_id, to_id) - ActiveRecord::Base.connection.execute <<~ROUTES_UPDATE - WITH routes_to_update AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} ( - SELECT - routes.id, - users.name AS correct_name - FROM - routes - INNER JOIN namespaces ON routes.source_id = namespaces.id - INNER JOIN users ON namespaces.owner_id = users.id - WHERE - namespaces.type IS NULL - AND routes.source_type = 'Namespace' - AND namespaces.id BETWEEN #{from_id} AND #{to_id} - AND (routes.name != users.name OR routes.name IS NULL) - ) - UPDATE - routes - SET - name = correct_name - FROM - routes_to_update - WHERE - routes_to_update.id = routes.id - ROUTES_UPDATE - end - end - end -end diff --git a/lib/gitlab/background_migration/fix_user_project_route_names.rb b/lib/gitlab/background_migration/fix_user_project_route_names.rb deleted file mode 100644 index e534f2449aa..00000000000 --- a/lib/gitlab/background_migration/fix_user_project_route_names.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This migration fixes the routes.name for all user-projects that have names - # that don't start with the users name. - # For more info see https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/23272 - class FixUserProjectRouteNames - def perform(from_id, to_id) - ActiveRecord::Base.connection.execute <<~ROUTES_UPDATE - WITH routes_to_update AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} ( - SELECT - routes.id, - users.name || ' / ' || projects.name AS correct_name - FROM - routes - INNER JOIN projects ON routes.source_id = projects.id - INNER JOIN namespaces ON projects.namespace_id = namespaces.id - INNER JOIN users ON namespaces.owner_id = users.id - WHERE - routes.source_type = 'Project' - AND routes.id BETWEEN #{from_id} AND #{to_id} - AND namespaces.type IS NULL - AND (routes.name NOT LIKE users.name || '%' OR routes.name IS NULL) - ) - UPDATE - routes - SET - name = routes_to_update.correct_name - FROM - routes_to_update - WHERE - routes_to_update.id = routes.id - ROUTES_UPDATE - end - end - end -end diff --git a/lib/gitlab/background_migration/link_lfs_objects_projects.rb b/lib/gitlab/background_migration/link_lfs_objects_projects.rb deleted file mode 100644 index 983470c5121..00000000000 --- a/lib/gitlab/background_migration/link_lfs_objects_projects.rb +++ /dev/null @@ -1,82 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Create missing LfsObjectsProject records for forks - class LinkLfsObjectsProjects - # Model specifically used for migration. - class LfsObjectsProject < ActiveRecord::Base - include EachBatch - - self.table_name = 'lfs_objects_projects' - - def self.linkable - where( - <<~SQL - lfs_objects_projects.project_id IN ( - SELECT fork_network_members.forked_from_project_id - FROM fork_network_members - WHERE fork_network_members.forked_from_project_id IS NOT NULL - ) - SQL - ) - end - end - - # Model specifically used for migration. - class ForkNetworkMember < ActiveRecord::Base - include EachBatch - - self.table_name = 'fork_network_members' - - def self.without_lfs_object(lfs_object_id) - where( - <<~SQL - fork_network_members.project_id NOT IN ( - SELECT lop.project_id - FROM lfs_objects_projects lop - WHERE lop.lfs_object_id = #{lfs_object_id} - ) - SQL - ) - end - end - - BATCH_SIZE = 1000 - - def perform(start_id, end_id) - lfs_objects_projects = - Gitlab::BackgroundMigration::LinkLfsObjectsProjects::LfsObjectsProject - .linkable - .where(id: start_id..end_id) - - return if lfs_objects_projects.empty? - - lfs_objects_projects.find_each do |lop| - ForkNetworkMember - .select("#{lop.lfs_object_id}, fork_network_members.project_id, NOW(), NOW()") - .without_lfs_object(lop.lfs_object_id) - .where(forked_from_project_id: lop.project_id) - .each_batch(of: BATCH_SIZE) do |batch, index| - execute <<~SQL - INSERT INTO lfs_objects_projects (lfs_object_id, project_id, created_at, updated_at) - #{batch.to_sql} - SQL - - logger.info(message: "LinkLfsObjectsProjects: created missing LfsObjectsProject records for LfsObject #{lop.lfs_object_id}") - end - end - end - - private - - def execute(sql) - ::ActiveRecord::Base.connection.execute(sql) - end - - def logger - @logger ||= Gitlab::BackgroundMigration::Logger.build - end - end - end -end diff --git a/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb b/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb deleted file mode 100644 index 36a339c6b80..00000000000 --- a/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class is responsible to update all sha256 fingerprints within the keys table - class MigrateFingerprintSha256WithinKeys - # Temporary AR table for keys - class Key < ActiveRecord::Base - include EachBatch - - self.table_name = 'keys' - self.inheritance_column = :_type_disabled - end - - TEMP_TABLE = 'tmp_fingerprint_sha256_migration' - - def perform(start_id, stop_id) - ActiveRecord::Base.transaction do - execute(<<~SQL) - CREATE TEMPORARY TABLE #{TEMP_TABLE} - (id bigint primary key, fingerprint_sha256 bytea not null) - ON COMMIT DROP - SQL - - fingerprints = [] - Key.where(id: start_id..stop_id, fingerprint_sha256: nil).find_each do |regular_key| - if fingerprint = generate_ssh_public_key(regular_key.key) - bytea = ActiveRecord::Base.connection.escape_bytea(Base64.decode64(fingerprint)) - - fingerprints << { - id: regular_key.id, - fingerprint_sha256: bytea - } - end - end - - ApplicationRecord.legacy_bulk_insert(TEMP_TABLE, fingerprints) # rubocop:disable Gitlab/BulkInsert - - execute("ANALYZE #{TEMP_TABLE}") - - execute(<<~SQL) - UPDATE keys - SET fingerprint_sha256 = t.fingerprint_sha256 - FROM #{TEMP_TABLE} t - WHERE keys.id = t.id - SQL - end - end - - private - - def generate_ssh_public_key(regular_key) - Gitlab::SSHPublicKey.new(regular_key).fingerprint("SHA256")&.gsub("SHA256:", "") - end - - def execute(query) - ActiveRecord::Base.connection.execute(query) - end - end - end -end diff --git a/lib/gitlab/background_migration/migrate_pages_metadata.rb b/lib/gitlab/background_migration/migrate_pages_metadata.rb deleted file mode 100644 index 68fd0c17d29..00000000000 --- a/lib/gitlab/background_migration/migrate_pages_metadata.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Class that will insert record into project_pages_metadata - # for each existing project - class MigratePagesMetadata - def perform(start_id, stop_id) - perform_on_relation(Project.where(id: start_id..stop_id)) - end - - def perform_on_relation(relation) - successful_pages_deploy = <<~SQL - SELECT TRUE - FROM ci_builds - WHERE ci_builds.type = 'GenericCommitStatus' - AND ci_builds.status = 'success' - AND ci_builds.stage = 'deploy' - AND ci_builds.name = 'pages:deploy' - AND ci_builds.project_id = projects.id - LIMIT 1 - SQL - - select_from = relation - .select("projects.id", "COALESCE((#{successful_pages_deploy}), FALSE)") - .to_sql - - ActiveRecord::Base.connection_pool.with_connection do |connection| - connection.execute <<~SQL - INSERT INTO project_pages_metadata (project_id, deployed) - #{select_from} - ON CONFLICT (project_id) DO NOTHING - SQL - end - end - end - end -end diff --git a/lib/gitlab/background_migration/migrate_security_scans.rb b/lib/gitlab/background_migration/migrate_security_scans.rb deleted file mode 100644 index 0ae984f2dbc..00000000000 --- a/lib/gitlab/background_migration/migrate_security_scans.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # rubocop: disable Style/Documentation - class MigrateSecurityScans - def perform(start_id, stop_id) - end - end - end -end - -Gitlab::BackgroundMigration::MigrateSecurityScans.prepend_mod_with('Gitlab::BackgroundMigration::MigrateSecurityScans') diff --git a/lib/gitlab/background_migration/migrate_to_hashed_storage.rb b/lib/gitlab/background_migration/migrate_to_hashed_storage.rb deleted file mode 100644 index 4054db4fb87..00000000000 --- a/lib/gitlab/background_migration/migrate_to_hashed_storage.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Background migration to move any legacy project to Hashed Storage - class MigrateToHashedStorage - def perform - batch_size = helper.batch_size - legacy_projects_count = Project.with_unmigrated_storage.count - - if storage_migrator.rollback_pending? - logger.warn( - migrator: 'MigrateToHashedStorage', - message: 'Aborting an storage rollback operation currently in progress' - ) - - storage_migrator.abort_rollback! - end - - if legacy_projects_count == 0 - logger.info( - migrator: 'MigrateToHashedStorage', - message: 'There are no projects requiring migration to Hashed Storage' - ) - - return - end - - logger.info( - migrator: 'MigrateToHashedStorage', - message: "Enqueuing migration of #{legacy_projects_count} projects in batches of #{batch_size}" - ) - - helper.project_id_batches_migration do |start, finish| - storage_migrator.bulk_schedule_migration(start: start, finish: finish) - - logger.info( - migrator: 'MigrateToHashedStorage', - message: "Enqueuing migration of projects in batches of #{batch_size} from ID=#{start} to ID=#{finish}", - batch_from: start, - batch_to: finish - ) - end - end - - private - - def helper - Gitlab::HashedStorage::RakeHelper - end - - def storage_migrator - @storage_migrator ||= Gitlab::HashedStorage::Migrator.new - end - - def logger - @logger ||= ::Gitlab::BackgroundMigration::Logger.build - end - end - end -end diff --git a/lib/gitlab/background_migration/move_epic_issues_after_epics.rb b/lib/gitlab/background_migration/move_epic_issues_after_epics.rb deleted file mode 100644 index 174994c7862..00000000000 --- a/lib/gitlab/background_migration/move_epic_issues_after_epics.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # rubocop: disable Style/Documentation - class MoveEpicIssuesAfterEpics - def perform(start_id, stop_id) - end - end - end -end - -Gitlab::BackgroundMigration::MoveEpicIssuesAfterEpics.prepend_mod_with('Gitlab::BackgroundMigration::MoveEpicIssuesAfterEpics') diff --git a/lib/gitlab/background_migration/populate_any_approval_rule_for_merge_requests.rb b/lib/gitlab/background_migration/populate_any_approval_rule_for_merge_requests.rb deleted file mode 100644 index 890a43800c9..00000000000 --- a/lib/gitlab/background_migration/populate_any_approval_rule_for_merge_requests.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This background migration creates any approver rule records according - # to the given merge request IDs range. A _single_ INSERT is issued for the given range. - class PopulateAnyApprovalRuleForMergeRequests - def perform(from_id, to_id) - end - end - end -end - -Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForMergeRequests.prepend_mod_with('Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForMergeRequests') diff --git a/lib/gitlab/background_migration/populate_any_approval_rule_for_projects.rb b/lib/gitlab/background_migration/populate_any_approval_rule_for_projects.rb deleted file mode 100644 index ac7ed18ba14..00000000000 --- a/lib/gitlab/background_migration/populate_any_approval_rule_for_projects.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This background migration creates any approver rule records according - # to the given project IDs range. A _single_ INSERT is issued for the given range. - class PopulateAnyApprovalRuleForProjects - def perform(from_id, to_id) - end - end - end -end - -Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForProjects.prepend_mod_with('Gitlab::BackgroundMigration::PopulateAnyApprovalRuleForProjects') diff --git a/lib/gitlab/background_migration/populate_canonical_emails.rb b/lib/gitlab/background_migration/populate_canonical_emails.rb deleted file mode 100644 index 052e75c5655..00000000000 --- a/lib/gitlab/background_migration/populate_canonical_emails.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Class to populate new rows of UserCanonicalEmail based on existing email addresses - class PopulateCanonicalEmails - def perform(start_id, stop_id) - ActiveRecord::Base.connection.execute <<~SQL - INSERT INTO - user_canonical_emails ( - user_id, - canonical_email, - created_at, - updated_at - ) - SELECT users.id AS user_id, - concat(translate(split_part(split_part(users.email, '@', 1), '+', 1), '.', ''), '@gmail.com') AS canonical_email, - NOW() AS created_at, - NOW() AS updated_at - FROM users - WHERE users.email ILIKE '%@gmail.com' - AND users.id BETWEEN #{start_id} AND #{stop_id} - ON CONFLICT DO NOTHING; - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities.rb b/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities.rb deleted file mode 100644 index 68c91650d93..00000000000 --- a/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class updates vulnerabilities entities with state dismissed - class PopulateDismissedStateForVulnerabilities - class Vulnerability < ActiveRecord::Base # rubocop:disable Style/Documentation - self.table_name = 'vulnerabilities' - end - - def perform(*vulnerability_ids) - Vulnerability.where(id: vulnerability_ids).update_all(state: 2) - PopulateMissingVulnerabilityDismissalInformation.new.perform(*vulnerability_ids) - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_has_vulnerabilities.rb b/lib/gitlab/background_migration/populate_has_vulnerabilities.rb deleted file mode 100644 index 28ff2070209..00000000000 --- a/lib/gitlab/background_migration/populate_has_vulnerabilities.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class populates missing dismissal information for - # vulnerability entries. - class PopulateHasVulnerabilities - class ProjectSetting < ActiveRecord::Base # rubocop:disable Style/Documentation - self.table_name = 'project_settings' - - def self.upsert_for(project_ids) - connection.execute(upsert_sql % { project_ids: project_ids.join(', ') }) - end - - def self.upsert_sql - <<~SQL - WITH upsert_data (project_id, has_vulnerabilities, created_at, updated_at) AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} ( - SELECT projects.id, true, current_timestamp, current_timestamp FROM projects WHERE projects.id IN (%{project_ids}) - ) - INSERT INTO project_settings - (project_id, has_vulnerabilities, created_at, updated_at) - (SELECT * FROM upsert_data) - ON CONFLICT (project_id) - DO UPDATE SET - has_vulnerabilities = true, - updated_at = EXCLUDED.updated_at - SQL - end - end - - class Vulnerability < ActiveRecord::Base # rubocop:disable Style/Documentation - include EachBatch - - self.table_name = 'vulnerabilities' - end - - def perform(*project_ids) - ProjectSetting.upsert_for(project_ids) - rescue StandardError => e - log_error(e, project_ids) - ensure - log_info(project_ids) - end - - private - - def log_error(error, project_ids) - ::Gitlab::BackgroundMigration::Logger.error( - migrator: self.class.name, - message: error.message, - project_ids: project_ids - ) - end - - def log_info(project_ids) - ::Gitlab::BackgroundMigration::Logger.info( - migrator: self.class.name, - message: 'Projects has been processed to populate `has_vulnerabilities` information', - count: project_ids.length - ) - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb deleted file mode 100644 index 28cc4a5e3fa..00000000000 --- a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This background migration creates records on merge_request_assignees according - # to the given merge request IDs range. A _single_ INSERT is issued for the given range. - # This is required for supporting multiple assignees on merge requests. - class PopulateMergeRequestAssigneesTable - def perform(from_id, to_id) - select_sql = - MergeRequest - .where(merge_request_assignees_not_exists_clause) - .where(id: from_id..to_id) - .where.not(assignee_id: nil) - .select(:id, :assignee_id) - .to_sql - - execute("INSERT INTO merge_request_assignees (merge_request_id, user_id) #{select_sql}") - end - - def perform_all_sync(batch_size:) - MergeRequest.each_batch(of: batch_size) do |batch| - range = batch.pluck('MIN(id)', 'MAX(id)').first - - perform(*range) - end - end - - private - - def merge_request_assignees_not_exists_clause - <<~SQL - NOT EXISTS (SELECT 1 FROM merge_request_assignees - WHERE merge_request_assignees.merge_request_id = merge_requests.id) - SQL - end - - def execute(sql) - @connection ||= ActiveRecord::Base.connection - @connection.execute(sql) - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information.rb b/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information.rb deleted file mode 100644 index 04342fdabd4..00000000000 --- a/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class populates missing dismissal information for - # vulnerability entries. - class PopulateMissingVulnerabilityDismissalInformation - class Vulnerability < ActiveRecord::Base # rubocop:disable Style/Documentation - include EachBatch - - self.table_name = 'vulnerabilities' - - has_one :finding, class_name: '::Gitlab::BackgroundMigration::PopulateMissingVulnerabilityDismissalInformation::Finding' - - scope :broken, -> { where('state = 2 AND (dismissed_at IS NULL OR dismissed_by_id IS NULL)') } - - def copy_dismissal_information - return unless finding&.dismissal_feedback - - update_columns( - dismissed_at: finding.dismissal_feedback.created_at, - dismissed_by_id: finding.dismissal_feedback.author_id - ) - end - end - - class Finding < ActiveRecord::Base # rubocop:disable Style/Documentation - include ShaAttribute - include ::Gitlab::Utils::StrongMemoize - - self.table_name = 'vulnerability_occurrences' - - sha_attribute :project_fingerprint - - def dismissal_feedback - strong_memoize(:dismissal_feedback) do - Feedback.dismissal.where(category: report_type, project_fingerprint: project_fingerprint, project_id: project_id).first - end - end - end - - class Feedback < ActiveRecord::Base # rubocop:disable Style/Documentation - DISMISSAL_TYPE = 0 - - self.table_name = 'vulnerability_feedback' - - scope :dismissal, -> { where(feedback_type: DISMISSAL_TYPE) } - end - - def perform(*vulnerability_ids) - Vulnerability.includes(:finding).where(id: vulnerability_ids).each { |vulnerability| populate_for(vulnerability) } - - log_info(vulnerability_ids) - end - - private - - def populate_for(vulnerability) - log_warning(vulnerability) unless vulnerability.copy_dismissal_information - rescue StandardError => error - log_error(error, vulnerability) - end - - def log_info(vulnerability_ids) - ::Gitlab::BackgroundMigration::Logger.info( - migrator: self.class.name, - message: 'Dismissal information has been copied', - count: vulnerability_ids.length - ) - end - - def log_warning(vulnerability) - ::Gitlab::BackgroundMigration::Logger.warn( - migrator: self.class.name, - message: 'Could not update vulnerability!', - vulnerability_id: vulnerability.id - ) - end - - def log_error(error, vulnerability) - ::Gitlab::BackgroundMigration::Logger.error( - migrator: self.class.name, - message: error.message, - vulnerability_id: vulnerability.id - ) - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb b/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb deleted file mode 100644 index ed7ffce8018..00000000000 --- a/lib/gitlab/background_migration/populate_personal_snippet_statistics.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class creates/updates those personal snippets statistics - # that haven't been created nor initialized. - # It also updates the related root storage namespace stats - class PopulatePersonalSnippetStatistics - def perform(snippet_ids) - personal_snippets(snippet_ids).group_by(&:author).each do |author, author_snippets| - upsert_snippet_statistics(author_snippets) - update_namespace_statistics(author.namespace) - end - end - - private - - def personal_snippets(snippet_ids) - PersonalSnippet - .where(id: snippet_ids) - .includes(author: :namespace) - .includes(:statistics) - .includes(snippet_repository: :shard) - end - - def upsert_snippet_statistics(snippets) - snippets.each do |snippet| - response = Snippets::UpdateStatisticsService.new(snippet).execute - - error_message("#{response.message} snippet: #{snippet.id}") if response.error? - end - end - - def update_namespace_statistics(namespace) - Namespaces::StatisticsRefresherService.new.execute(namespace) - rescue StandardError => e - error_message("Error updating statistics for namespace #{namespace.id}: #{e.message}") - end - - def logger - @logger ||= Gitlab::BackgroundMigration::Logger.build - end - - def error_message(message) - logger.error(message: "Snippet Statistics Migration: #{message}") - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_project_snippet_statistics.rb b/lib/gitlab/background_migration/populate_project_snippet_statistics.rb deleted file mode 100644 index 37af320f044..00000000000 --- a/lib/gitlab/background_migration/populate_project_snippet_statistics.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class creates/updates those project snippets statistics - # that haven't been created nor initialized. - # It also updates the related project statistics and its root storage namespace stats - class PopulateProjectSnippetStatistics - def perform(snippet_ids) - project_snippets(snippet_ids).group_by(&:namespace_id).each do |namespace_id, namespace_snippets| - namespace_snippets.group_by(&:project).each do |project, snippets| - upsert_snippet_statistics(snippets) - update_project_statistics(project) - rescue StandardError - error_message("Error updating statistics for project #{project.id}") - end - - update_namespace_statistics(namespace_snippets.first.project.root_namespace) - rescue StandardError => e - error_message("Error updating statistics for namespace #{namespace_id}: #{e.message}") - end - end - - private - - def project_snippets(snippet_ids) - ProjectSnippet - .select('snippets.*, projects.namespace_id') - .where(id: snippet_ids) - .joins(:project) - .includes(:statistics) - .includes(snippet_repository: :shard) - .includes(project: [:route, :statistics, :namespace]) - end - - def upsert_snippet_statistics(snippets) - snippets.each do |snippet| - response = Snippets::UpdateStatisticsService.new(snippet).execute - - error_message("#{response.message} snippet: #{snippet.id}") if response.error? - end - end - - def logger - @logger ||= Gitlab::BackgroundMigration::Logger.build - end - - def error_message(message) - logger.error(message: "Snippet Statistics Migration: #{message}") - end - - def update_project_statistics(project) - project.statistics&.refresh!(only: [:snippets_size]) - end - - def update_namespace_statistics(namespace) - Namespaces::StatisticsRefresherService.new.execute(namespace) - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id.rb b/lib/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id.rb deleted file mode 100644 index 8241fea66db..00000000000 --- a/lib/gitlab/background_migration/populate_vulnerability_feedback_pipeline_id.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class updates vulnerability feedback entities with no pipeline id assigned. - class PopulateVulnerabilityFeedbackPipelineId - def perform(project_ids) - end - end - end -end - -Gitlab::BackgroundMigration::PopulateVulnerabilityFeedbackPipelineId.prepend_mod_with('Gitlab::BackgroundMigration::PopulateVulnerabilityFeedbackPipelineId') diff --git a/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb b/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb deleted file mode 100644 index 9a9f23e29ea..00000000000 --- a/lib/gitlab/background_migration/populate_vulnerability_historical_statistics.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This class creates/updates those project historical vulnerability statistics - # that haven't been created nor initialized. It should only be executed in EE. - class PopulateVulnerabilityHistoricalStatistics - def perform(project_ids, retention_period = 90) - end - end - end -end - -Gitlab::BackgroundMigration::PopulateVulnerabilityHistoricalStatistics.prepend_mod_with('Gitlab::BackgroundMigration::PopulateVulnerabilityHistoricalStatistics') diff --git a/lib/gitlab/background_migration/prune_orphaned_geo_events.rb b/lib/gitlab/background_migration/prune_orphaned_geo_events.rb deleted file mode 100644 index 0efbe72775c..00000000000 --- a/lib/gitlab/background_migration/prune_orphaned_geo_events.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true -# -# rubocop:disable Style/Documentation - -# This job is added to fix https://gitlab.com/gitlab-org/gitlab/issues/30229 -# It's not used anywhere else. -# Can be removed in GitLab 13.* -module Gitlab - module BackgroundMigration - class PruneOrphanedGeoEvents - def perform(table_name) - end - end - end -end - -Gitlab::BackgroundMigration::PruneOrphanedGeoEvents.prepend_mod_with('Gitlab::BackgroundMigration::PruneOrphanedGeoEvents') diff --git a/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id.rb b/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id.rb deleted file mode 100644 index b66fdfd5c65..00000000000 --- a/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # rubocop:disable Style/Documentation - class RecalculateProjectAuthorizationsWithMinMaxUserId - def perform(min_user_id, max_user_id) - User.where(id: min_user_id..max_user_id).find_each do |user| - service = Users::RefreshAuthorizedProjectsService.new( - user, - incorrect_auth_found_callback: - ->(project_id, access_level) do - logger.info(message: 'Removing ProjectAuthorizations', - user_id: user.id, - project_id: project_id, - access_level: access_level) - end, - missing_auth_found_callback: - ->(project_id, access_level) do - logger.info(message: 'Creating ProjectAuthorizations', - user_id: user.id, - project_id: project_id, - access_level: access_level) - end - ) - - service.execute - end - end - - private - - def logger - @logger ||= Gitlab::BackgroundMigration::Logger.build - end - 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 deleted file mode 100644 index 17ef6dec4c0..00000000000 --- a/lib/gitlab/background_migration/remove_duplicate_cs_findings.rb +++ /dev/null @@ -1,13 +0,0 @@ -# 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_mod_with('Gitlab::BackgroundMigration::RemoveDuplicateCsFindings') diff --git a/lib/gitlab/background_migration/remove_duplicated_cs_findings_without_vulnerability_id.rb b/lib/gitlab/background_migration/remove_duplicated_cs_findings_without_vulnerability_id.rb deleted file mode 100644 index e5772fc7375..00000000000 --- a/lib/gitlab/background_migration/remove_duplicated_cs_findings_without_vulnerability_id.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class RemoveDuplicatedCsFindingsWithoutVulnerabilityId - def perform(start_id, stop_id) - end - end - end -end - -Gitlab::BackgroundMigration::RemoveDuplicatedCsFindingsWithoutVulnerabilityId.prepend_mod_with('Gitlab::BackgroundMigration::RemoveDuplicatedCsFindingsWithoutVulnerabilityId') diff --git a/lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb b/lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb deleted file mode 100644 index cb6a600a525..00000000000 --- a/lib/gitlab/background_migration/remove_inaccessible_epic_todos.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # rubocop:disable Style/Documentation - class RemoveInaccessibleEpicTodos - def perform(start_id, stop_id) - end - end - end -end - -Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos.prepend_mod_with('Gitlab::BackgroundMigration::RemoveInaccessibleEpicTodos') diff --git a/lib/gitlab/background_migration/remove_undefined_vulnerability_confidence_level.rb b/lib/gitlab/background_migration/remove_undefined_vulnerability_confidence_level.rb deleted file mode 100644 index 4be61bfb689..00000000000 --- a/lib/gitlab/background_migration/remove_undefined_vulnerability_confidence_level.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class RemoveUndefinedVulnerabilityConfidenceLevel - def perform(start_id, stop_id) - end - end - end -end - -Gitlab::BackgroundMigration::RemoveUndefinedVulnerabilityConfidenceLevel.prepend_mod_with('Gitlab::BackgroundMigration::RemoveUndefinedVulnerabilityConfidenceLevel') diff --git a/lib/gitlab/background_migration/replace_blocked_by_links.rb b/lib/gitlab/background_migration/replace_blocked_by_links.rb deleted file mode 100644 index 0c29887bb00..00000000000 --- a/lib/gitlab/background_migration/replace_blocked_by_links.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class ReplaceBlockedByLinks - class IssueLink < ActiveRecord::Base - self.table_name = 'issue_links' - end - - def perform(start_id, stop_id) - blocked_by_links = IssueLink.where(id: start_id..stop_id).where(link_type: 2) - - ActiveRecord::Base.transaction do - # There could be two edge cases: - # 1) issue1 is blocked by issue2 AND issue2 blocks issue1 (type 1) - # 2) issue1 is blocked by issue2 AND issue2 is related to issue1 (type 0) - # In both cases cases we couldn't convert blocked by relation to - # `issue2 blocks issue` because there is already a link with the same - # source/target id. To avoid these conflicts, we first delete any - # "opposite" links before we update `blocked by` relation. This - # should be rare as we have a pre-create check which checks if issues - # are already linked - opposite_ids = blocked_by_links - .select('opposite_links.id') - .joins('INNER JOIN issue_links as opposite_links ON issue_links.source_id = opposite_links.target_id AND issue_links.target_id = opposite_links.source_id') - IssueLink.where(id: opposite_ids).delete_all - - blocked_by_links.update_all('source_id=target_id,target_id=source_id,link_type=1') - end - end - end - end -end diff --git a/lib/gitlab/background_migration/reset_merge_status.rb b/lib/gitlab/background_migration/reset_merge_status.rb deleted file mode 100644 index d040b4931be..00000000000 --- a/lib/gitlab/background_migration/reset_merge_status.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Updates the range of given MRs to merge_status "unchecked", if they're opened - # and mergeable. - class ResetMergeStatus - def perform(from_id, to_id) - relation = MergeRequest.where(id: from_id..to_id, - state_id: 1, # opened - merge_status: 'can_be_merged') - - relation.update_all(merge_status: 'unchecked') - end - end - end -end diff --git a/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects.rb b/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects.rb deleted file mode 100644 index 0053cafb4ac..00000000000 --- a/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Resets inconsistent state of shared_runners_enabled for projects that have been transferred - class ResetSharedRunnersForTransferredProjects - # Model specifically used for migration. - class Namespace < ActiveRecord::Base - include EachBatch - - self.table_name = 'namespaces' - end - - # Model specifically used for migration. - class Project < ActiveRecord::Base - self.table_name = 'projects' - end - - def perform(start_id, stop_id) - Project.reset_column_information - - Namespace.where(id: start_id..stop_id).each_batch(of: 1_000) do |relation| - ids = relation.where(shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false).select(:id) - - Project.where(namespace_id: ids).update_all(shared_runners_enabled: false) - end - end - end - end -end diff --git a/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb b/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb deleted file mode 100644 index 527dd2a0a83..00000000000 --- a/lib/gitlab/background_migration/set_merge_request_diff_files_count.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # Sets the MergeRequestDiff#files_count value for old rows - class SetMergeRequestDiffFilesCount - # Some historic data has a *lot* of files. Apply a sentinel to these cases - FILES_COUNT_SENTINEL = 2**15 - 1 - - def self.count_subquery - <<~SQL - files_count = ( - SELECT LEAST(#{FILES_COUNT_SENTINEL}, count(*)) - FROM merge_request_diff_files - WHERE merge_request_diff_files.merge_request_diff_id = merge_request_diffs.id - ) - SQL - end - - class MergeRequestDiff < ActiveRecord::Base # rubocop:disable Style/Documentation - include EachBatch - - self.table_name = 'merge_request_diffs' - end - - def perform(start_id, end_id) - MergeRequestDiff.where(id: start_id..end_id).each_batch do |relation| - relation.update_all(self.class.count_subquery) - end - end - end - end -end diff --git a/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent.rb b/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent.rb deleted file mode 100644 index 9e330f7c008..00000000000 --- a/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This background migration updates children of group to match visibility of a parent - class UpdateExistingSubgroupToMatchVisibilityLevelOfParent - def perform(parents_groups_ids, level) - groups_ids = Gitlab::ObjectHierarchy.new(Group.where(id: parents_groups_ids)) - .base_and_descendants - .where("visibility_level > ?", level) - .select(:id) - - return if groups_ids.empty? - - Group - .where(id: groups_ids) - .update_all(visibility_level: level) - end - end - end -end diff --git a/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth.rb b/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth.rb deleted file mode 100644 index d97765cd398..00000000000 --- a/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class UpdateExistingUsersThatRequireTwoFactorAuth # rubocop:disable Metrics/ClassLength - def perform(start_id, stop_id) - ActiveRecord::Base.connection.execute <<~SQL - UPDATE - users - SET - require_two_factor_authentication_from_group = FALSE - WHERE - users.id BETWEEN #{start_id} - AND #{stop_id} - AND users.require_two_factor_authentication_from_group = TRUE - AND users.id NOT IN ( SELECT DISTINCT - users_groups_query.user_id - FROM ( - SELECT - users.id AS user_id, - members.source_id AS group_ids - FROM - users - LEFT JOIN members ON members.source_type = 'Namespace' - AND members.requested_at IS NULL - AND members.user_id = users.id - AND members.type = 'GroupMember' - WHERE - users.require_two_factor_authentication_from_group = TRUE - AND users.id BETWEEN #{start_id} - AND #{stop_id}) AS users_groups_query - INNER JOIN LATERAL ( WITH RECURSIVE "base_and_ancestors" AS ( - ( - SELECT - "namespaces"."type", - "namespaces"."id", - "namespaces"."parent_id", - "namespaces"."require_two_factor_authentication" - FROM - "namespaces" - WHERE - "namespaces"."type" = 'Group' - AND "namespaces"."id" = users_groups_query.group_ids) - UNION ( - SELECT - "namespaces"."type", - "namespaces"."id", - "namespaces"."parent_id", - "namespaces"."require_two_factor_authentication" - FROM - "namespaces", - "base_and_ancestors" - WHERE - "namespaces"."type" = 'Group' - AND "namespaces"."id" = "base_and_ancestors"."parent_id")), - "base_and_descendants" AS ( - ( - SELECT - "namespaces"."type", - "namespaces"."id", - "namespaces"."parent_id", - "namespaces"."require_two_factor_authentication" - FROM - "namespaces" - WHERE - "namespaces"."type" = 'Group' - AND "namespaces"."id" = users_groups_query.group_ids) - UNION ( - SELECT - "namespaces"."type", - "namespaces"."id", - "namespaces"."parent_id", - "namespaces"."require_two_factor_authentication" - FROM - "namespaces", - "base_and_descendants" - WHERE - "namespaces"."type" = 'Group' - AND "namespaces"."parent_id" = "base_and_descendants"."id")) - SELECT - "namespaces".* - FROM (( - SELECT - "namespaces"."type", - "namespaces"."id", - "namespaces"."parent_id", - "namespaces"."require_two_factor_authentication" - FROM - "base_and_ancestors" AS "namespaces" - WHERE - "namespaces"."type" = 'Group') - UNION ( - SELECT - "namespaces"."type", - "namespaces"."id", - "namespaces"."parent_id", - "namespaces"."require_two_factor_authentication" - FROM - "base_and_descendants" AS "namespaces" - WHERE - "namespaces"."type" = 'Group')) namespaces - WHERE - "namespaces"."type" = 'Group' - AND "namespaces"."require_two_factor_authentication" = TRUE) AS hierarchy_tree ON TRUE); - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/update_location_fingerprint_for_container_scanning_findings.rb b/lib/gitlab/background_migration/update_location_fingerprint_for_container_scanning_findings.rb deleted file mode 100644 index 054b918dade..00000000000 --- a/lib/gitlab/background_migration/update_location_fingerprint_for_container_scanning_findings.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class UpdateLocationFingerprintForContainerScanningFindings - def perform(start_id, stop_id) - end - end - end -end - -Gitlab::BackgroundMigration::UpdateLocationFingerprintForContainerScanningFindings.prepend_mod_with('Gitlab::BackgroundMigration::UpdateLocationFingerprintForContainerScanningFindings') diff --git a/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb b/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb deleted file mode 100644 index 1cc03f061fb..00000000000 --- a/lib/gitlab/background_migration/update_vulnerabilities_from_dismissal_feedback.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # rubocop: disable Style/Documentation - class UpdateVulnerabilitiesFromDismissalFeedback - def perform(project_id) - end - end - end -end - -Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback.prepend_mod_with('Gitlab::BackgroundMigration::UpdateVulnerabilitiesFromDismissalFeedback') diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb index 5afcb386152..5fd422db722 100644 --- a/lib/gitlab/quick_actions/issue_actions.rb +++ b/lib/gitlab/quick_actions/issue_actions.rb @@ -292,17 +292,11 @@ module Gitlab condition do current_user.can?(:set_issue_crm_contacts, quick_action_target) end + execution_message do + _('One or more contacts were successfully added.') + end command :add_contacts do |contact_emails| - result = ::Issues::SetCrmContactsService - .new(project: project, current_user: current_user, params: { add_emails: contact_emails.split(' ') }) - .execute(quick_action_target) - - @execution_message[:add_contacts] = - if result.success? - _('One or more contacts were successfully added.') - else - result.message - end + @updates[:add_contacts] = contact_emails.split(' ') end desc _('Remove customer relation contacts') @@ -312,17 +306,11 @@ module Gitlab condition do current_user.can?(:set_issue_crm_contacts, quick_action_target) end + execution_message do + _('One or more contacts were successfully removed.') + end command :remove_contacts do |contact_emails| - result = ::Issues::SetCrmContactsService - .new(project: project, current_user: current_user, params: { remove_emails: contact_emails.split(' ') }) - .execute(quick_action_target) - - @execution_message[:remove_contacts] = - if result.success? - _('One or more contacts were successfully removed.') - else - result.message - end + @updates[:remove_contacts] = contact_emails.split(' ') end private diff --git a/lib/sidebars/concerns/container_with_html_options.rb b/lib/sidebars/concerns/container_with_html_options.rb index 79dddd309b5..796b7cbe275 100644 --- a/lib/sidebars/concerns/container_with_html_options.rb +++ b/lib/sidebars/concerns/container_with_html_options.rb @@ -3,6 +3,8 @@ module Sidebars module Concerns module ContainerWithHtmlOptions + include LinkWithHtmlOptions + # The attributes returned from this method # will be applied to helper methods like # `link_to` or the div containing the container. diff --git a/lib/sidebars/concerns/link_with_html_options.rb b/lib/sidebars/concerns/link_with_html_options.rb new file mode 100644 index 00000000000..4ca748e0a71 --- /dev/null +++ b/lib/sidebars/concerns/link_with_html_options.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Sidebars + module Concerns + module LinkWithHtmlOptions + # add on specific items as the pertain to `link_to` objects specifically + def link_html_options + container_html_options.tap do |html_options| + html_options[:class] = [*html_options[:class], 'gl-link'].join(' ') + end + end + end + end +end diff --git a/lib/sidebars/menu_item.rb b/lib/sidebars/menu_item.rb index 1375f9fffca..7ed3e47ae34 100644 --- a/lib/sidebars/menu_item.rb +++ b/lib/sidebars/menu_item.rb @@ -2,6 +2,8 @@ module Sidebars class MenuItem + include ::Sidebars::Concerns::LinkWithHtmlOptions + attr_reader :title, :link, :active_routes, :item_id, :container_html_options, :sprite_icon, :sprite_icon_html_options, :hint_html_options def initialize(title:, link:, active_routes:, item_id: nil, container_html_options: {}, sprite_icon: nil, sprite_icon_html_options: {}, hint_html_options: {}) diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js index 3e5822778a5..9622cdf0cfe 100644 --- a/spec/frontend/integrations/edit/components/integration_form_spec.js +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -1,6 +1,8 @@ +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import * as Sentry from '@sentry/browser'; import { setHTMLFixture } from 'helpers/fixtures'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; - import { mockIntegrationProps } from 'jest/integrations/edit/mock_data'; import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue'; import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue'; @@ -11,19 +13,27 @@ import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_field import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue'; import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; +import waitForPromises from 'helpers/wait_for_promises'; import { integrationLevels, - TEST_INTEGRATION_EVENT, + I18N_SUCCESSFUL_CONNECTION_MESSAGE, + VALIDATE_INTEGRATION_FORM_EVENT, SAVE_INTEGRATION_EVENT, + I18N_DEFAULT_ERROR_MESSAGE, } from '~/integrations/constants'; import { createStore } from '~/integrations/edit/store'; import eventHub from '~/integrations/edit/event_hub'; +import httpStatus from '~/lib/utils/http_status'; jest.mock('~/integrations/edit/event_hub'); +jest.mock('@sentry/browser'); describe('IntegrationForm', () => { + const mockToastShow = jest.fn(); + let wrapper; let dispatch; + let mockAxios; const createComponent = ({ customStateProps = {}, @@ -39,6 +49,9 @@ describe('IntegrationForm', () => { wrapper = shallowMountExtended(IntegrationForm, { propsData: { ...props, formSelector: '.test' }, + provide: { + glFeatures: featureFlags, + }, store, stubs: { OverrideDropdown, @@ -47,15 +60,21 @@ describe('IntegrationForm', () => { JiraTriggerFields, TriggerFields, }, - provide: { - glFeatures: featureFlags, + mocks: { + $toast: { + show: mockToastShow, + }, }, }); }; - afterEach(() => { - wrapper.destroy(); - }); + const createForm = ({ isValid = true } = {}) => { + const mockForm = document.createElement('form'); + jest.spyOn(document, 'querySelector').mockReturnValue(mockForm); + jest.spyOn(mockForm, 'checkValidity').mockReturnValue(isValid); + + return mockForm; + }; const findOverrideDropdown = () => wrapper.findComponent(OverrideDropdown); const findActiveCheckbox = () => wrapper.findComponent(ActiveCheckbox); @@ -68,6 +87,15 @@ describe('IntegrationForm', () => { const findJiraIssuesFields = () => wrapper.findComponent(JiraIssuesFields); const findTriggerFields = () => wrapper.findComponent(TriggerFields); + beforeEach(() => { + mockAxios = new MockAdapter(axios); + }); + + afterEach(() => { + wrapper.destroy(); + mockAxios.restore(); + }); + describe('template', () => { describe('integrationLevel is instance', () => { it('renders ConfirmationModal', () => { @@ -399,18 +427,9 @@ describe('IntegrationForm', () => { }); describe('when `test` button is clicked', () => { - let mockForm; - - describe.each` - formValid - ${true} - ${false} - `('when form checkValidity returns $formValid', ({ formValid }) => { - beforeEach(() => { - mockForm = document.createElement('form'); - jest.spyOn(document, 'querySelector').mockReturnValue(mockForm); - jest.spyOn(mockForm, 'checkValidity').mockReturnValue(formValid); - + describe('when form is invalid', () => { + it('emits `VALIDATE_INTEGRATION_FORM_EVENT` event to the event hub', () => { + createForm({ isValid: false }); createComponent({ customStateProps: { showActive: true, @@ -419,14 +438,70 @@ describe('IntegrationForm', () => { }); findTestButton().vm.$emit('click', new Event('click')); + + expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT); }); + }); + + describe('when form is valid', () => { + const mockTestPath = '/test'; - it('dispatches setIsTesting action', () => { - expect(dispatch).toHaveBeenCalledWith('setIsTesting', true); + beforeEach(() => { + createForm({ isValid: true }); + createComponent({ + customStateProps: { + showActive: true, + canTest: true, + testPath: mockTestPath, + }, + }); }); - it(`emits \`TEST_INTEGRATION_EVENT\` event with payload \`${formValid}\``, () => { - expect(eventHub.$emit).toHaveBeenCalledWith(TEST_INTEGRATION_EVENT, formValid); + describe('buttons', () => { + beforeEach(async () => { + await findTestButton().vm.$emit('click', new Event('click')); + }); + + it('sets test button `loading` prop to `true`', () => { + expect(findTestButton().props('loading')).toBe(true); + }); + + it('sets save button `disabled` prop to `true`', () => { + expect(findSaveButton().props('disabled')).toBe(true); + }); + }); + + describe.each` + scenario | replyStatus | errorMessage | expectToast | expectSentry + ${'when "test settings" request fails'} | ${httpStatus.INTERNAL_SERVER_ERROR} | ${undefined} | ${I18N_DEFAULT_ERROR_MESSAGE} | ${true} + ${'when "test settings" returns an error'} | ${httpStatus.OK} | ${'an error'} | ${'an error'} | ${false} + ${'when "test settings" succeeds'} | ${httpStatus.OK} | ${undefined} | ${I18N_SUCCESSFUL_CONNECTION_MESSAGE} | ${false} + `('$scenario', ({ replyStatus, errorMessage, expectToast, expectSentry }) => { + beforeEach(async () => { + mockAxios.onPut(mockTestPath).replyOnce(replyStatus, { + error: Boolean(errorMessage), + message: errorMessage, + }); + + await findTestButton().vm.$emit('click', new Event('click')); + await waitForPromises(); + }); + + it(`calls toast with '${expectToast}'`, () => { + expect(mockToastShow).toHaveBeenCalledWith(expectToast); + }); + + it('sets `loading` prop of test button to `false`', () => { + expect(findTestButton().props('loading')).toBe(false); + }); + + it('sets save button `disabled` prop to `false`', () => { + expect(findSaveButton().props('disabled')).toBe(false); + }); + + it(`${expectSentry ? 'does' : 'does not'} capture exception in Sentry`, () => { + expect(Sentry.captureException).toHaveBeenCalledTimes(expectSentry ? 1 : 0); + }); }); }); }); diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js index 3cf69c65126..dff435578a7 100644 --- a/spec/frontend/integrations/edit/store/actions_spec.js +++ b/spec/frontend/integrations/edit/store/actions_spec.js @@ -5,7 +5,6 @@ import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/c import { setOverride, setIsSaving, - setIsTesting, setIsResetting, requestResetIntegration, receiveResetIntegrationSuccess, @@ -46,12 +45,6 @@ describe('Integration form store actions', () => { }); }); - describe('setIsTesting', () => { - it('should commit isTesting mutation', () => { - return testAction(setIsTesting, true, state, [{ type: types.SET_IS_TESTING, payload: true }]); - }); - }); - describe('setIsResetting', () => { it('should commit isResetting mutation', () => { return testAction(setIsResetting, true, state, [ diff --git a/spec/frontend/integrations/edit/store/getters_spec.js b/spec/frontend/integrations/edit/store/getters_spec.js index ad7a887dff2..037629d22a6 100644 --- a/spec/frontend/integrations/edit/store/getters_spec.js +++ b/spec/frontend/integrations/edit/store/getters_spec.js @@ -54,20 +54,15 @@ describe('Integration form store getters', () => { describe('isDisabled', () => { it.each` - isSaving | isTesting | isResetting | expected - ${false} | ${false} | ${false} | ${false} - ${true} | ${false} | ${false} | ${true} - ${false} | ${true} | ${false} | ${true} - ${false} | ${false} | ${true} | ${true} - ${false} | ${true} | ${true} | ${true} - ${true} | ${false} | ${true} | ${true} - ${true} | ${true} | ${false} | ${true} - ${true} | ${true} | ${true} | ${true} + isSaving | isResetting | expected + ${false} | ${false} | ${false} + ${true} | ${false} | ${true} + ${false} | ${true} | ${true} + ${true} | ${true} | ${true} `( - 'when isSaving = $isSaving, isTesting = $isTesting, isResetting = $isResetting then isDisabled = $expected', - ({ isSaving, isTesting, isResetting, expected }) => { + 'when isSaving = $isSaving, isResetting = $isResetting then isDisabled = $expected', + ({ isSaving, isResetting, expected }) => { mutations[types.SET_IS_SAVING](state, isSaving); - mutations[types.SET_IS_TESTING](state, isTesting); mutations[types.SET_IS_RESETTING](state, isResetting); expect(isDisabled(state)).toBe(expected); diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js index 18faa2f6bba..9f27a7ed6be 100644 --- a/spec/frontend/integrations/edit/store/mutations_spec.js +++ b/spec/frontend/integrations/edit/store/mutations_spec.js @@ -25,14 +25,6 @@ describe('Integration form store mutations', () => { }); }); - describe(`${types.SET_IS_TESTING}`, () => { - it('sets isTesting', () => { - mutations[types.SET_IS_TESTING](state, true); - - expect(state.isTesting).toBe(true); - }); - }); - describe(`${types.SET_IS_RESETTING}`, () => { it('sets isResetting', () => { mutations[types.SET_IS_RESETTING](state, true); diff --git a/spec/frontend/integrations/edit/store/state_spec.js b/spec/frontend/integrations/edit/store/state_spec.js index 6cd84836395..5582be7fd3c 100644 --- a/spec/frontend/integrations/edit/store/state_spec.js +++ b/spec/frontend/integrations/edit/store/state_spec.js @@ -6,7 +6,6 @@ describe('Integration form state factory', () => { defaultState: null, customState: {}, isSaving: false, - isTesting: false, isResetting: false, override: false, isLoadingJiraIssueTypes: false, diff --git a/spec/frontend/integrations/integration_settings_form_spec.js b/spec/frontend/integrations/integration_settings_form_spec.js index a71fe9dd0c4..344d4799889 100644 --- a/spec/frontend/integrations/integration_settings_form_spec.js +++ b/spec/frontend/integrations/integration_settings_form_spec.js @@ -2,13 +2,7 @@ import MockAdaptor from 'axios-mock-adapter'; import IntegrationSettingsForm from '~/integrations/integration_settings_form'; import eventHub from '~/integrations/edit/event_hub'; import axios from '~/lib/utils/axios_utils'; -import toast from '~/vue_shared/plugins/global_toast'; -import { - I18N_SUCCESSFUL_CONNECTION_MESSAGE, - I18N_DEFAULT_ERROR_MESSAGE, - TEST_INTEGRATION_EVENT, - SAVE_INTEGRATION_EVENT, -} from '~/integrations/constants'; +import { SAVE_INTEGRATION_EVENT } from '~/integrations/constants'; import waitForPromises from 'helpers/wait_for_promises'; jest.mock('~/vue_shared/plugins/global_toast'); @@ -29,6 +23,10 @@ describe('IntegrationSettingsForm', () => { integrationSettingsForm.init(); }); + afterEach(() => { + eventHub.dispose(); // clear event hub handlers + }); + describe('constructor', () => { it('should initialize form element refs on class object', () => { expect(integrationSettingsForm.$form).toBeDefined(); @@ -47,88 +45,11 @@ describe('IntegrationSettingsForm', () => { beforeEach(() => { mockAxios = new MockAdaptor(axios); jest.spyOn(axios, 'put'); - jest.spyOn(integrationSettingsForm, 'testSettings'); jest.spyOn(integrationSettingsForm.$form, 'submit'); }); afterEach(() => { mockAxios.restore(); - eventHub.dispose(); // clear event hub handlers - }); - - describe('when event hub receives `TEST_INTEGRATION_EVENT`', () => { - describe('when form is valid', () => { - it('should make an ajax request with provided `formData`', async () => { - eventHub.$emit(TEST_INTEGRATION_EVENT, true); - await waitForPromises(); - - expect(axios.put).toHaveBeenCalledWith( - integrationSettingsForm.testEndPoint, - new FormData(integrationSettingsForm.$form), - ); - }); - - it('should show success message if test is successful', async () => { - jest.spyOn(integrationSettingsForm.$form, 'submit').mockImplementation(() => {}); - - mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, { - error: false, - }); - - eventHub.$emit(TEST_INTEGRATION_EVENT, true); - await waitForPromises(); - - expect(toast).toHaveBeenCalledWith(I18N_SUCCESSFUL_CONNECTION_MESSAGE); - }); - - it('should show error message if ajax request responds with test error', async () => { - const errorMessage = 'Test failed.'; - const serviceResponse = 'some error'; - - mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, { - error: true, - message: errorMessage, - service_response: serviceResponse, - test_failed: false, - }); - - eventHub.$emit(TEST_INTEGRATION_EVENT, true); - await waitForPromises(); - - expect(toast).toHaveBeenCalledWith(`${errorMessage} ${serviceResponse}`); - }); - - it('should show error message if ajax request failed', async () => { - mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError(); - - eventHub.$emit(TEST_INTEGRATION_EVENT, true); - await waitForPromises(); - - expect(toast).toHaveBeenCalledWith(I18N_DEFAULT_ERROR_MESSAGE); - }); - - it('should always dispatch `setIsTesting` with `false` once request is completed', async () => { - const dispatchSpy = mockStoreDispatch(); - mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError(); - - eventHub.$emit(TEST_INTEGRATION_EVENT, true); - await waitForPromises(); - - expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false); - }); - }); - - describe('when form is invalid', () => { - it('should dispatch `setIsTesting` with `false` and not call `testSettings`', async () => { - const dispatchSpy = mockStoreDispatch(); - - eventHub.$emit(TEST_INTEGRATION_EVENT, false); - await waitForPromises(); - - expect(dispatchSpy).toHaveBeenCalledWith('setIsTesting', false); - expect(integrationSettingsForm.testSettings).not.toHaveBeenCalled(); - }); - }); }); describe('when event hub receives `SAVE_INTEGRATION_EVENT`', () => { @@ -137,7 +58,6 @@ describe('IntegrationSettingsForm', () => { eventHub.$emit(SAVE_INTEGRATION_EVENT, true); await waitForPromises(); - expect(integrationSettingsForm.$form.submit).toHaveBeenCalled(); expect(integrationSettingsForm.$form.submit).toHaveBeenCalledTimes(1); }); }); diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js index 552a1c6fcde..2d5e0cfd615 100644 --- a/spec/frontend/snippets/components/snippet_header_spec.js +++ b/spec/frontend/snippets/components/snippet_header_spec.js @@ -252,7 +252,7 @@ describe('Snippet header component', () => { disabled: false, href: `/foo/-/snippets/new`, text: 'New snippet', - variant: 'success', + variant: 'confirm', }, ]), ); diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb deleted file mode 100644 index 1e72b249c19..00000000000 --- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount do - let(:namespaces_table) { table(:namespaces) } - let(:projects_table) { table(:projects) } - let(:merge_requests_table) { table(:merge_requests) } - let(:merge_request_diffs_table) { table(:merge_request_diffs) } - let(:merge_request_diff_commits_table) { table(:merge_request_diff_commits) } - - let(:namespace) { namespaces_table.create!(name: 'gitlab-org', path: 'gitlab-org') } - let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: namespace.id) } - let(:merge_request) do - merge_requests_table.create!(target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: 'mr name', - title: 'mr name') - end - - def create_diff!(name, commits: 0) - mr_diff = merge_request_diffs_table.create!( - merge_request_id: merge_request.id) - - commits.times do |i| - merge_request_diff_commits_table.create!( - merge_request_diff_id: mr_diff.id, - relative_order: i, sha: i) - end - - mr_diff - end - - describe '#perform' do - it 'migrates diffs that have no commits' do - diff = create_diff!('with_multiple_commits', commits: 0) - - subject.perform(diff.id, diff.id) - - expect(diff.reload.commits_count).to eq(0) - end - - it 'skips diffs that have commits_count already set' do - timestamp = 2.days.ago - diff = merge_request_diffs_table.create!( - merge_request_id: merge_request.id, - commits_count: 0, - updated_at: timestamp) - - subject.perform(diff.id, diff.id) - - expect(diff.reload.updated_at).to be_within(1.second).of(timestamp) - end - - it 'migrates multiple diffs to the correct values' do - diffs = Array.new(3).map.with_index { |_, i| create_diff!(i, commits: 3) } - - subject.perform(diffs.first.id, diffs.last.id) - - diffs.each do |diff| - expect(diff.reload.commits_count).to eq(3) - end - end - end -end diff --git a/spec/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule_spec.rb b/spec/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule_spec.rb deleted file mode 100644 index 0b29163671c..00000000000 --- a/spec/lib/gitlab/background_migration/add_modified_to_approval_merge_request_rule_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::AddModifiedToApprovalMergeRequestRule, schema: 20181228175414 do - let(:determine_if_rules_are_modified) { described_class.new } - - let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab') } - let(:projects) { table(:projects) } - let(:normal_project) { projects.create!(namespace_id: namespace.id) } - let(:overridden_project) { projects.create!(namespace_id: namespace.id) } - let(:rules) { table(:approval_merge_request_rules) } - let(:project_rules) { table(:approval_project_rules) } - let(:sources) { table(:approval_merge_request_rule_sources) } - let(:merge_requests) { table(:merge_requests) } - let(:groups) { table(:namespaces) } - let(:mr_groups) { table(:approval_merge_request_rules_groups) } - let(:project_groups) { table(:approval_project_rules_groups) } - - before do - project_rule = project_rules.create!(project_id: normal_project.id, approvals_required: 3, name: 'test rule') - overridden_project_rule = project_rules.create!(project_id: overridden_project.id, approvals_required: 5, name: 'other test rule') - overridden_project_rule_two = project_rules.create!(project_id: overridden_project.id, approvals_required: 7, name: 'super cool rule') - - merge_request = merge_requests.create!(target_branch: 'feature', source_branch: 'default', source_project_id: normal_project.id, target_project_id: normal_project.id) - overridden_merge_request = merge_requests.create!(target_branch: 'feature-2', source_branch: 'default', source_project_id: overridden_project.id, target_project_id: overridden_project.id) - - merge_rule = rules.create!(merge_request_id: merge_request.id, approvals_required: 3, name: 'test rule') - overridden_merge_rule = rules.create!(merge_request_id: overridden_merge_request.id, approvals_required: 6, name: 'other test rule') - overridden_merge_rule_two = rules.create!(merge_request_id: overridden_merge_request.id, approvals_required: 7, name: 'super cool rule') - - sources.create!(approval_project_rule_id: project_rule.id, approval_merge_request_rule_id: merge_rule.id) - sources.create!(approval_project_rule_id: overridden_project_rule.id, approval_merge_request_rule_id: overridden_merge_rule.id) - sources.create!(approval_project_rule_id: overridden_project_rule_two.id, approval_merge_request_rule_id: overridden_merge_rule_two.id) - - group1 = groups.create!(name: "group1", path: "test_group1", type: 'Group') - group2 = groups.create!(name: "group2", path: "test_group2", type: 'Group') - group3 = groups.create!(name: "group3", path: "test_group3", type: 'Group') - - project_groups.create!(approval_project_rule_id: overridden_project_rule_two.id, group_id: group1.id) - project_groups.create!(approval_project_rule_id: overridden_project_rule_two.id, group_id: group2.id) - project_groups.create!(approval_project_rule_id: overridden_project_rule_two.id, group_id: group3.id) - - mr_groups.create!(approval_merge_request_rule_id: overridden_merge_rule.id, group_id: group1.id) - mr_groups.create!(approval_merge_request_rule_id: overridden_merge_rule_two.id, group_id: group2.id) - end - - describe '#perform' do - it 'changes the correct rules' do - original_count = rules.all.count - - determine_if_rules_are_modified.perform(rules.minimum(:id), rules.maximum(:id)) - - results = rules.where(modified_from_project_rule: true) - - expect(results.count).to eq 2 - expect(results.collect(&:name)).to eq(['other test rule', 'super cool rule']) - expect(rules.count).to eq original_count - end - end -end diff --git a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb b/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb deleted file mode 100644 index 1404ada3647..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_deployment_clusters_from_deployments_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillDeploymentClustersFromDeployments, :migration, schema: 20181228175414 do - subject { described_class.new } - - describe '#perform' do - it 'backfills deployment_cluster for all deployments in the given range with a non-null cluster_id' do - deployment_clusters = table(:deployment_clusters) - - namespace = table(:namespaces).create!(name: 'the-namespace', path: 'the-path') - project = table(:projects).create!(name: 'the-project', namespace_id: namespace.id) - environment = table(:environments).create!(name: 'the-environment', project_id: project.id, slug: 'slug') - cluster = table(:clusters).create!(name: 'the-cluster') - - deployment_data = { cluster_id: cluster.id, project_id: project.id, environment_id: environment.id, ref: 'abc', tag: false, sha: 'sha', status: 1 } - expected_deployment_1 = create_deployment(**deployment_data) - create_deployment(**deployment_data, cluster_id: nil) # no cluster_id - expected_deployment_2 = create_deployment(**deployment_data) - out_of_range_deployment = create_deployment(**deployment_data, cluster_id: cluster.id) # expected to be out of range - - # to test "ON CONFLICT DO NOTHING" - existing_record_for_deployment_2 = deployment_clusters.create!( - deployment_id: expected_deployment_2.id, - cluster_id: expected_deployment_2.cluster_id, - kubernetes_namespace: 'production' - ) - - subject.perform(expected_deployment_1.id, out_of_range_deployment.id - 1) - - expect(deployment_clusters.all.pluck(:deployment_id, :cluster_id, :kubernetes_namespace)).to contain_exactly( - [expected_deployment_1.id, cluster.id, nil], - [expected_deployment_2.id, cluster.id, existing_record_for_deployment_2.kubernetes_namespace] - ) - end - - def create_deployment(**data) - @iid ||= 0 - @iid += 1 - table(:deployments).create!(iid: @iid, **data) - end - end -end diff --git a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb b/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb deleted file mode 100644 index 9194525e713..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_environment_id_deployment_merge_requests_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillEnvironmentIdDeploymentMergeRequests, schema: 20181228175414 do - let(:environments) { table(:environments) } - let(:merge_requests) { table(:merge_requests) } - let(:deployments) { table(:deployments) } - let(:deployment_merge_requests) { table(:deployment_merge_requests) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - - subject(:migration) { described_class.new } - - it 'correctly backfills environment_id column' do - namespace = namespaces.create!(name: 'foo', path: 'foo') - project = projects.create!(namespace_id: namespace.id) - - production = environments.create!(project_id: project.id, name: 'production', slug: 'production') - staging = environments.create!(project_id: project.id, name: 'staging', slug: 'staging') - - mr = merge_requests.create!(source_branch: 'x', target_branch: 'master', target_project_id: project.id) - - deployment1 = deployments.create!(environment_id: staging.id, iid: 1, project_id: project.id, ref: 'master', tag: false, sha: '123abcdef', status: 1) - deployment2 = deployments.create!(environment_id: production.id, iid: 2, project_id: project.id, ref: 'master', tag: false, sha: '123abcdef', status: 1) - deployment3 = deployments.create!(environment_id: production.id, iid: 3, project_id: project.id, ref: 'master', tag: false, sha: '123abcdef', status: 1) - - # mr is tracked twice in production through deployment2 and deployment3 - deployment_merge_requests.create!(deployment_id: deployment1.id, merge_request_id: mr.id) - deployment_merge_requests.create!(deployment_id: deployment2.id, merge_request_id: mr.id) - deployment_merge_requests.create!(deployment_id: deployment3.id, merge_request_id: mr.id) - - expect(deployment_merge_requests.where(environment_id: nil).count).to eq(3) - - migration.backfill_range(1, mr.id) - - expect(deployment_merge_requests.where(environment_id: nil).count).to be_zero - expect(deployment_merge_requests.count).to eq(2) - - production_deployments = deployment_merge_requests.where(environment_id: production.id) - expect(production_deployments.count).to eq(1) - expect(production_deployments.first.deployment_id).to eq(deployment2.id) - - expect(deployment_merge_requests.where(environment_id: staging.id).count).to eq(1) - end -end diff --git a/spec/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules_spec.rb b/spec/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules_spec.rb deleted file mode 100644 index d33f52514da..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_merge_request_cleanup_schedules_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillMergeRequestCleanupSchedules, schema: 20181228175414 do - let(:merge_requests) { table(:merge_requests) } - let(:cleanup_schedules) { table(:merge_request_cleanup_schedules) } - let(:metrics) { table(:merge_request_metrics) } - - let(:namespace) { table(:namespaces).create!(name: 'name', path: 'path') } - let(:project) { table(:projects).create!(namespace_id: namespace.id) } - - subject { described_class.new } - - describe '#perform' do - let!(:open_mr) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master') } - - let!(:closed_mr_1) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) } - let!(:closed_mr_2) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) } - let!(:closed_mr_1_metrics) { metrics.create!(merge_request_id: closed_mr_1.id, target_project_id: project.id, latest_closed_at: Time.current, created_at: Time.current, updated_at: Time.current) } - let!(:closed_mr_2_metrics) { metrics.create!(merge_request_id: closed_mr_2.id, target_project_id: project.id, latest_closed_at: Time.current, created_at: Time.current, updated_at: Time.current) } - let!(:closed_mr_2_cleanup_schedule) { cleanup_schedules.create!(merge_request_id: closed_mr_2.id, scheduled_at: Time.current) } - - let!(:merged_mr_1) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 3) } - let!(:merged_mr_2) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 3, updated_at: Time.current) } - let!(:merged_mr_1_metrics) { metrics.create!(merge_request_id: merged_mr_1.id, target_project_id: project.id, merged_at: Time.current, created_at: Time.current, updated_at: Time.current) } - - let!(:closed_mr_3) { merge_requests.create!(target_project_id: project.id, source_branch: 'master', target_branch: 'master', state_id: 2) } - let!(:closed_mr_3_metrics) { metrics.create!(merge_request_id: closed_mr_3.id, target_project_id: project.id, latest_closed_at: Time.current, created_at: Time.current, updated_at: Time.current) } - - it 'creates records for all closed and merged merge requests in range' do - expect(Gitlab::BackgroundMigration::Logger).to receive(:info).with( - message: 'Backfilled merge_request_cleanup_schedules records', - count: 3 - ) - - subject.perform(open_mr.id, merged_mr_2.id) - - aggregate_failures do - expect(cleanup_schedules.all.pluck(:merge_request_id)) - .to contain_exactly(closed_mr_1.id, closed_mr_2.id, merged_mr_1.id, merged_mr_2.id) - expect(cleanup_schedules.find_by(merge_request_id: closed_mr_1.id).scheduled_at.to_s) - .to eq((closed_mr_1_metrics.latest_closed_at + 14.days).to_s) - expect(cleanup_schedules.find_by(merge_request_id: closed_mr_2.id).scheduled_at.to_s) - .to eq(closed_mr_2_cleanup_schedule.scheduled_at.to_s) - expect(cleanup_schedules.find_by(merge_request_id: merged_mr_1.id).scheduled_at.to_s) - .to eq((merged_mr_1_metrics.merged_at + 14.days).to_s) - expect(cleanup_schedules.find_by(merge_request_id: merged_mr_2.id).scheduled_at.to_s) - .to eq((merged_mr_2.updated_at + 14.days).to_s) - end - end - end -end diff --git a/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb deleted file mode 100644 index 0f8adca2ca4..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_namespace_settings_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillNamespaceSettings, schema: 20181228175414 do - let(:namespaces) { table(:namespaces) } - let(:namespace_settings) { table(:namespace_settings) } - let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } - - subject { described_class.new } - - describe '#perform' do - it 'creates settings for all projects in range' do - namespaces.create!(id: 5, name: 'test1', path: 'test1') - namespaces.create!(id: 7, name: 'test2', path: 'test2') - namespaces.create!(id: 8, name: 'test3', path: 'test3') - - subject.perform(5, 7) - - expect(namespace_settings.all.pluck(:namespace_id)).to contain_exactly(5, 7) - end - end -end diff --git a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb deleted file mode 100644 index e6b0db2ab73..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_project_settings_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillProjectSettings, schema: 20181228175414 do - let(:projects) { table(:projects) } - let(:project_settings) { table(:project_settings) } - let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } - let(:project) { projects.create!(namespace_id: namespace.id) } - - subject { described_class.new } - - describe '#perform' do - it 'creates settings for all projects in range' do - projects.create!(id: 5, namespace_id: namespace.id) - projects.create!(id: 7, namespace_id: namespace.id) - projects.create!(id: 8, namespace_id: namespace.id) - - subject.perform(5, 7) - - expect(project_settings.all.pluck(:project_id)).to contain_exactly(5, 7) - end - end -end diff --git a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb b/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb deleted file mode 100644 index 3468df3dccd..00000000000 --- a/spec/lib/gitlab/background_migration/backfill_push_rules_id_in_projects_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::BackfillPushRulesIdInProjects, :migration, schema: 20181228175414 do - let(:push_rules) { table(:push_rules) } - let(:projects) { table(:projects) } - let(:project_settings) { table(:project_settings) } - let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') } - - subject { described_class.new } - - describe '#perform' do - it 'creates new project push_rules for all push rules in the range' do - project_1 = projects.create!(id: 1, namespace_id: namespace.id) - project_2 = projects.create!(id: 2, namespace_id: namespace.id) - project_3 = projects.create!(id: 3, namespace_id: namespace.id) - project_settings_1 = project_settings.create!(project_id: project_1.id) - project_settings_2 = project_settings.create!(project_id: project_2.id) - project_settings_3 = project_settings.create!(project_id: project_3.id) - push_rule_1 = push_rules.create!(id: 5, is_sample: false, project_id: project_1.id) - push_rule_2 = push_rules.create!(id: 6, is_sample: false, project_id: project_2.id) - push_rules.create!(id: 8, is_sample: false, project_id: 3) - - subject.perform(5, 7) - - expect(project_settings_1.reload.push_rule_id).to eq(push_rule_1.id) - expect(project_settings_2.reload.push_rule_id).to eq(push_rule_2.id) - expect(project_settings_3.reload.push_rule_id).to be_nil - end - end -end diff --git a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb b/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb deleted file mode 100644 index 35ec8be691a..00000000000 --- a/spec/lib/gitlab/background_migration/fix_promoted_epics_discussion_ids_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::FixPromotedEpicsDiscussionIds, schema: 20181228175414 do - let(:namespaces) { table(:namespaces) } - let(:users) { table(:users) } - let(:epics) { table(:epics) } - let(:notes) { table(:notes) } - - let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } - let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } - let(:epic1) { epics.create!(id: 1, author_id: user.id, iid: 1, group_id: namespace.id, title: 'Epic with discussion', title_html: 'Epic with discussion') } - - def create_note(discussion_id) - notes.create!(note: 'note comment', - noteable_id: epic1.id, - noteable_type: 'Epic', - discussion_id: discussion_id) - end - - def expect_valid_discussion_id(id) - expect(id).to match(/\A\h{40}\z/) - end - - describe '#perform with batch of discussion ids' do - it 'updates discussion ids' do - note1 = create_note('00000000') - note2 = create_note('00000000') - note3 = create_note('10000000') - - subject.perform(%w(00000000 10000000)) - - expect_valid_discussion_id(note1.reload.discussion_id) - expect_valid_discussion_id(note2.reload.discussion_id) - expect_valid_discussion_id(note3.reload.discussion_id) - expect(note1.discussion_id).to eq(note2.discussion_id) - expect(note1.discussion_id).not_to eq(note3.discussion_id) - end - - it 'skips notes with discussion id not in range' do - note4 = create_note('20000000') - - subject.perform(%w(00000000 10000000)) - - expect(note4.reload.discussion_id).to eq('20000000') - end - end -end diff --git a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb deleted file mode 100644 index 95509f9b897..00000000000 --- a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::FixUserNamespaceNames, schema: 20181228175414 do - let(:namespaces) { table(:namespaces) } - let(:users) { table(:users) } - let(:user) { users.create!(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') } - - context 'updating the namespace names' do - it 'updates a user namespace within range' do - user2 = users.create!(name: "Other user's full name", projects_limit: 10, username: 'also-not-null', email: '2') - user_namespace1 = namespaces.create!( - id: 2, - owner_id: user.id, - name: "Should be the user's name", - path: user.username - ) - user_namespace2 = namespaces.create!( - id: 3, - owner_id: user2.id, - name: "Should also be the user's name", - path: user.username - ) - - described_class.new.perform(1, 5) - - expect(user_namespace1.reload.name).to eq("The user's full name") - expect(user_namespace2.reload.name).to eq("Other user's full name") - end - - it 'does not update namespaces out of range' do - user_namespace = namespaces.create!( - id: 6, - owner_id: user.id, - name: "Should be the user's name", - path: user.username - ) - - expect { described_class.new.perform(1, 5) } - .not_to change { user_namespace.reload.name } - end - - it 'does not update groups owned by the users' do - user_group = namespaces.create!( - id: 2, - owner_id: user.id, - name: 'A group name', - path: 'the-path', - type: 'Group' - ) - - expect { described_class.new.perform(1, 5) } - .not_to change { user_group.reload.name } - end - end - - context 'namespace route names' do - let(:routes) { table(:routes) } - let(:namespace) do - namespaces.create!( - id: 2, - owner_id: user.id, - name: "Will be updated to the user's name", - path: user.username - ) - end - - it "updates the route name if it didn't match the namespace" do - route = routes.create!(path: namespace.path, name: 'Incorrect name', source_type: 'Namespace', source_id: namespace.id) - - described_class.new.perform(1, 5) - - expect(route.reload.name).to eq("The user's full name") - end - - it 'updates the route name if it was nil match the namespace' do - route = routes.create!(path: namespace.path, name: nil, source_type: 'Namespace', source_id: namespace.id) - - described_class.new.perform(1, 5) - - expect(route.reload.name).to eq("The user's full name") - end - - it "doesn't update group routes" do - route = routes.create!(path: 'group-path', name: 'Group name', source_type: 'Group', source_id: namespace.id) - - expect { described_class.new.perform(1, 5) } - .not_to change { route.reload.name } - end - - it "doesn't touch routes for namespaces out of range" do - user_namespace = namespaces.create!( - id: 6, - owner_id: user.id, - name: "Should be the user's name", - path: user.username - ) - - expect { described_class.new.perform(1, 5) } - .not_to change { user_namespace.reload.name } - end - end -end diff --git a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb deleted file mode 100644 index b4444df674e..00000000000 --- a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb +++ /dev/null @@ -1,98 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, schema: 20181228175414 do - let(:namespaces) { table(:namespaces) } - let(:users) { table(:users) } - let(:routes) { table(:routes) } - let(:projects) { table(:projects) } - - let(:user) { users.create!(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') } - - let(:namespace) do - namespaces.create!( - owner_id: user.id, - name: "Should eventually be the user's name", - path: user.username - ) - end - - let(:project) do - projects.create!(namespace_id: namespace.id, name: 'Project Name') - end - - it "updates the route for a project if it did not match the user's name" do - route = routes.create!( - id: 1, - path: "#{user.username}/#{project.path}", - source_id: project.id, - source_type: 'Project', - name: 'Completely wrong' - ) - - described_class.new.perform(1, 5) - - expect(route.reload.name).to eq("The user's full name / Project Name") - end - - it 'updates the route for a project if the name was nil' do - route = routes.create!( - id: 1, - path: "#{user.username}/#{project.path}", - source_id: project.id, - source_type: 'Project', - name: nil - ) - - described_class.new.perform(1, 5) - - expect(route.reload.name).to eq("The user's full name / Project Name") - end - - it 'does not update routes that were are out of the range' do - route = routes.create!( - id: 6, - path: "#{user.username}/#{project.path}", - source_id: project.id, - source_type: 'Project', - name: 'Completely wrong' - ) - - expect { described_class.new.perform(1, 5) } - .not_to change { route.reload.name } - end - - it 'does not update routes for projects in groups owned by the user' do - group = namespaces.create!( - owner_id: user.id, - name: 'A group', - path: 'a-path', - type: '' - ) - project = projects.create!(namespace_id: group.id, name: 'Project Name') - route = routes.create!( - id: 1, - path: "#{group.path}/#{project.path}", - source_id: project.id, - source_type: 'Project', - name: 'Completely wrong' - ) - - expect { described_class.new.perform(1, 5) } - .not_to change { route.reload.name } - end - - it 'does not update routes for namespaces' do - route = routes.create!( - id: 1, - path: namespace.path, - source_id: namespace.id, - source_type: 'Namespace', - name: 'Completely wrong' - ) - - expect { described_class.new.perform(1, 5) } - .not_to change { route.reload.name } - end -end diff --git a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb b/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb deleted file mode 100644 index 64e8afedf52..00000000000 --- a/spec/lib/gitlab/background_migration/link_lfs_objects_projects_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::LinkLfsObjectsProjects, :migration, schema: 20181228175414 do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:fork_networks) { table(:fork_networks) } - let(:fork_network_members) { table(:fork_network_members) } - let(:lfs_objects) { table(:lfs_objects) } - let(:lfs_objects_projects) { table(:lfs_objects_projects) } - - let(:namespace) { namespaces.create!(name: 'GitLab', path: 'gitlab') } - - let(:fork_network) { fork_networks.create!(root_project_id: source_project.id) } - let(:another_fork_network) { fork_networks.create!(root_project_id: another_source_project.id) } - - let(:source_project) { projects.create!(namespace_id: namespace.id) } - let(:another_source_project) { projects.create!(namespace_id: namespace.id) } - let(:project) { projects.create!(namespace_id: namespace.id) } - let(:another_project) { projects.create!(namespace_id: namespace.id) } - let(:partially_linked_project) { projects.create!(namespace_id: namespace.id) } - let(:fully_linked_project) { projects.create!(namespace_id: namespace.id) } - - let(:lfs_object) { lfs_objects.create!(oid: 'abc123', size: 100) } - let(:another_lfs_object) { lfs_objects.create!(oid: 'def456', size: 200) } - - let!(:source_project_lop_1) do - lfs_objects_projects.create!( - lfs_object_id: lfs_object.id, - project_id: source_project.id - ) - end - - let!(:source_project_lop_2) do - lfs_objects_projects.create!( - lfs_object_id: another_lfs_object.id, - project_id: source_project.id - ) - end - - let!(:another_source_project_lop_1) do - lfs_objects_projects.create!( - lfs_object_id: lfs_object.id, - project_id: another_source_project.id - ) - end - - let!(:another_source_project_lop_2) do - lfs_objects_projects.create!( - lfs_object_id: another_lfs_object.id, - project_id: another_source_project.id - ) - end - - before do - stub_const("#{described_class}::BATCH_SIZE", 2) - - # Create links between projects - fork_network_members.create!(fork_network_id: fork_network.id, project_id: source_project.id, forked_from_project_id: nil) - - [project, partially_linked_project, fully_linked_project].each do |p| - fork_network_members.create!( - fork_network_id: fork_network.id, - project_id: p.id, - forked_from_project_id: fork_network.root_project_id - ) - end - - fork_network_members.create!(fork_network_id: another_fork_network.id, project_id: another_source_project.id, forked_from_project_id: nil) - fork_network_members.create!(fork_network_id: another_fork_network.id, project_id: another_project.id, forked_from_project_id: another_fork_network.root_project_id) - - # Links LFS objects to some projects - lfs_objects_projects.create!(lfs_object_id: lfs_object.id, project_id: fully_linked_project.id) - lfs_objects_projects.create!(lfs_object_id: another_lfs_object.id, project_id: fully_linked_project.id) - lfs_objects_projects.create!(lfs_object_id: lfs_object.id, project_id: partially_linked_project.id) - end - - context 'when there are LFS objects to be linked' do - it 'creates LfsObjectsProject records for forks based on the specified range of LfsObjectProject id' do - expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger| - expect(logger).to receive(:info).exactly(4).times - end - - expect { subject.perform(source_project_lop_1.id, another_source_project_lop_2.id) }.to change { lfs_objects_projects.count }.by(5) - - expect(lfs_object_ids_for(project)).to match_array(lfs_object_ids_for(source_project)) - expect(lfs_object_ids_for(another_project)).to match_array(lfs_object_ids_for(another_source_project)) - expect(lfs_object_ids_for(partially_linked_project)).to match_array(lfs_object_ids_for(source_project)) - - expect { subject.perform(source_project_lop_1.id, another_source_project_lop_2.id) }.not_to change { lfs_objects_projects.count } - end - end - - context 'when there are no LFS objects to be linked' do - before do - # Links LFS objects to all projects - projects.all.each do |p| - lfs_objects_projects.create!(lfs_object_id: lfs_object.id, project_id: p.id) - lfs_objects_projects.create!(lfs_object_id: another_lfs_object.id, project_id: p.id) - end - end - - it 'does not create LfsObjectProject records' do - expect { subject.perform(source_project_lop_1.id, another_source_project_lop_2.id) } - .not_to change { lfs_objects_projects.count } - end - end - - def lfs_object_ids_for(project) - lfs_objects_projects.where(project_id: project.id).pluck(:lfs_object_id) - end -end diff --git a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb b/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb deleted file mode 100644 index 4287d6723cf..00000000000 --- a/spec/lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::MigrateFingerprintSha256WithinKeys, schema: 20181228175414 do - subject(:fingerprint_migrator) { described_class.new } - - let(:key_table) { table(:keys) } - - before do - generate_fingerprints! - end - - it 'correctly creates a sha256 fingerprint for a key' do - key_1 = Key.find(1017) - key_2 = Key.find(1027) - - expect(key_1.fingerprint_md5).to eq('ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1') - expect(key_1.fingerprint_sha256).to eq(nil) - - expect(key_2.fingerprint_md5).to eq('39:e3:64:a6:24:ea:45:a2:8c:55:2a:e9:4d:4f:1f:b4') - expect(key_2.fingerprint_sha256).to eq(nil) - - query_count = ActiveRecord::QueryRecorder.new do - fingerprint_migrator.perform(1, 10000) - end.count - - expect(query_count).to eq(8) - - key_1.reload - key_2.reload - - expect(key_1.fingerprint_md5).to eq('ba:81:59:68:d7:6c:cd:02:02:bf:6a:9b:55:4e:af:d1') - expect(key_1.fingerprint_sha256).to eq('nUhzNyftwADy8AH3wFY31tAKs7HufskYTte2aXo/lCg') - - expect(key_2.fingerprint_md5).to eq('39:e3:64:a6:24:ea:45:a2:8c:55:2a:e9:4d:4f:1f:b4') - expect(key_2.fingerprint_sha256).to eq('zMNbLekgdjtcgDv8VSC0z5lpdACMG3Q4PUoIz5+H2jM') - end - - context 'with invalid keys' do - before do - key = Key.find(1017) - # double space after "ssh-rsa" leads to a - # OpenSSL::PKey::PKeyError in Net::SSH::KeyFactory.load_data_public_key - key.update_column(:key, key.key.gsub('ssh-rsa ', 'ssh-rsa ')) - end - - it 'ignores errors and does not set the fingerprint' do - fingerprint_migrator.perform(1, 10000) - - key_1 = Key.find(1017) - key_2 = Key.find(1027) - - expect(key_1.fingerprint_sha256).to be_nil - expect(key_2.fingerprint_sha256).not_to be_nil - end - end - - it 'migrates all keys' do - expect(Key.where(fingerprint_sha256: nil).count).to eq(Key.all.count) - - fingerprint_migrator.perform(1, 10000) - - expect(Key.where(fingerprint_sha256: nil).count).to eq(0) - end - - def generate_fingerprints! - values = "" - (1000..2000).to_a.each do |record| - key = base_key_for(record) - fingerprint = fingerprint_for(key) - - values += "(#{record}, #{record}, 'test-#{record}', '#{key}', '#{fingerprint}')," - end - - update_query = <<~SQL - INSERT INTO keys ( id, user_id, title, key, fingerprint ) - VALUES - #{values.chomp(",")}; - SQL - - ActiveRecord::Base.connection.execute(update_query) - end - - def base_key_for(record) - 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt0000k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=' - .gsub("0000", "%04d" % (record - 1)) # generate arbitrary keys with placeholder 0000 within the key above - end - - def fingerprint_for(key) - Gitlab::SSHPublicKey.new(key).fingerprint("md5") - end -end diff --git a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb b/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb deleted file mode 100644 index b6d93b9ff54..00000000000 --- a/spec/lib/gitlab/background_migration/migrate_pages_metadata_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::MigratePagesMetadata, schema: 20181228175414 do - let(:projects) { table(:projects) } - - subject(:migrate_pages_metadata) { described_class.new } - - describe '#perform' do - let(:namespaces) { table(:namespaces) } - let(:builds) { table(:ci_builds) } - let(:pages_metadata) { table(:project_pages_metadata) } - - it 'marks specified projects with successful pages deployment' do - namespace = namespaces.create!(name: 'gitlab', path: 'gitlab-org') - not_migrated_with_pages = projects.create!(namespace_id: namespace.id, name: 'Not Migrated With Pages') - builds.create!(project_id: not_migrated_with_pages.id, type: 'GenericCommitStatus', status: 'success', stage: 'deploy', name: 'pages:deploy') - - migrated = projects.create!(namespace_id: namespace.id, name: 'Migrated') - pages_metadata.create!(project_id: migrated.id, deployed: true) - - not_migrated_no_pages = projects.create!(namespace_id: namespace.id, name: 'Not Migrated No Pages') - project_not_in_relation_scope = projects.create!(namespace_id: namespace.id, name: 'Other') - - ids = [not_migrated_no_pages.id, not_migrated_with_pages.id, migrated.id] - - migrate_pages_metadata.perform(ids.min, ids.max) - - expect(pages_metadata.find_by_project_id(not_migrated_with_pages.id).deployed).to eq(true) - expect(pages_metadata.find_by_project_id(not_migrated_no_pages.id).deployed).to eq(false) - expect(pages_metadata.find_by_project_id(migrated.id).deployed).to eq(true) - expect(pages_metadata.find_by_project_id(project_not_in_relation_scope.id)).to be_nil - end - end -end diff --git a/spec/lib/gitlab/background_migration/migrate_to_hashed_storage_spec.rb b/spec/lib/gitlab/background_migration/migrate_to_hashed_storage_spec.rb deleted file mode 100644 index 0f7bb06e830..00000000000 --- a/spec/lib/gitlab/background_migration/migrate_to_hashed_storage_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -# rubocop:disable RSpec/FactoriesInMigrationSpecs -RSpec.describe Gitlab::BackgroundMigration::MigrateToHashedStorage, :sidekiq, :redis do - let(:migrator) { Gitlab::HashedStorage::Migrator.new } - - subject(:background_migration) { described_class.new } - - describe '#perform' do - let!(:project) { create(:project, :empty_repo, :legacy_storage) } - - context 'with pending rollback' do - it 'aborts rollback operation' do - Sidekiq::Testing.disable! do - Sidekiq::Client.push( - 'queue' => ::HashedStorage::ProjectRollbackWorker.queue, - 'class' => ::HashedStorage::ProjectRollbackWorker, - 'args' => [project.id] - ) - - expect { background_migration.perform }.to change { migrator.rollback_pending? }.from(true).to(false) - end - end - end - - it 'enqueues legacy projects to be migrated' do - Sidekiq::Testing.fake! do - expect { background_migration.perform }.to change { Sidekiq::Queues[::HashedStorage::MigratorWorker.queue].size }.by(1) - end - end - - context 'when executing all jobs' do - it 'migrates legacy projects' do - Sidekiq::Testing.inline! do - expect { background_migration.perform }.to change { project.reload.legacy_storage? }.from(true).to(false) - end - end - end - end -end -# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb b/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb deleted file mode 100644 index 944ee98ed4a..00000000000 --- a/spec/lib/gitlab/background_migration/populate_canonical_emails_spec.rb +++ /dev/null @@ -1,94 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateCanonicalEmails, :migration, schema: 20181228175414 do - let(:migration) { described_class.new } - - let_it_be(:users_table) { table(:users) } - let_it_be(:user_canonical_emails_table) { table(:user_canonical_emails) } - - let_it_be(:users) { users_table.all } - let_it_be(:user_canonical_emails) { user_canonical_emails_table.all } - - subject { migration.perform(1, 1) } - - describe 'gmail users' do - using RSpec::Parameterized::TableSyntax - - where(:original_email, :expected_result) do - 'legitimateuser@gmail.com' | 'legitimateuser@gmail.com' - 'userwithplus+somestuff@gmail.com' | 'userwithplus@gmail.com' - 'user.with.periods@gmail.com' | 'userwithperiods@gmail.com' - 'user.with.periods.and.plus+someotherstuff@gmail.com' | 'userwithperiodsandplus@gmail.com' - end - - with_them do - it 'generates the correct canonical email' do - create_user(email: original_email, id: 1) - - subject - - result = canonical_emails - expect(result.count).to eq 1 - expect(result.first).to match({ - 'user_id' => 1, - 'canonical_email' => expected_result - }) - end - end - end - - describe 'non gmail.com domain users' do - %w[ - legitimateuser@somedomain.com - userwithplus+somestuff@other.com - user.with.periods@gmail.org - user.with.periods.and.plus+someotherstuff@orangmail.com - ].each do |non_gmail_address| - it 'does not generate a canonical email' do - create_user(email: non_gmail_address, id: 1) - - subject - - expect(canonical_emails(user_id: 1).count).to eq 0 - end - end - end - - describe 'gracefully handles missing records' do - specify { expect { subject }.not_to raise_error } - end - - describe 'gracefully handles existing records, some of which may have an already-existing identical canonical_email field' do - let_it_be(:user_one) { create_user(email: "example.user@gmail.com", id: 1) } - let_it_be(:user_two) { create_user(email: "exampleuser@gmail.com", id: 2) } - let_it_be(:user_email_one) { user_canonical_emails.create!(canonical_email: "exampleuser@gmail.com", user_id: user_one.id) } - - subject { migration.perform(1, 2) } - - it 'only creates one record' do - subject - - expect(canonical_emails.count).not_to be_nil - end - end - - def create_user(attributes) - default_attributes = { - projects_limit: 0 - } - - users.create!(default_attributes.merge!(attributes)) - end - - def canonical_emails(user_id: nil) - filter_by_id = user_id ? "WHERE user_id = #{user_id}" : "" - - ApplicationRecord.connection.execute <<~SQL - SELECT canonical_email, user_id - FROM user_canonical_emails - #{filter_by_id}; - SQL - end -end diff --git a/spec/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities_spec.rb b/spec/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities_spec.rb deleted file mode 100644 index dc8c8c75b83..00000000000 --- a/spec/lib/gitlab/background_migration/populate_dismissed_state_for_vulnerabilities_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe ::Gitlab::BackgroundMigration::PopulateDismissedStateForVulnerabilities, schema: 20181228175414 do - let(:users) { table(:users) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:vulnerabilities) { table(:vulnerabilities) } - - let!(:namespace) { namespaces.create!(name: "foo", path: "bar") } - let!(:user) { users.create!(name: 'John Doe', email: 'test@example.com', projects_limit: 5) } - let!(:project) { projects.create!(namespace_id: namespace.id) } - let!(:vulnerability_params) do - { - project_id: project.id, - author_id: user.id, - title: 'Vulnerability', - severity: 5, - confidence: 5, - report_type: 5 - } - end - - let!(:vulnerability_1) { vulnerabilities.create!(vulnerability_params.merge(state: 1)) } - let!(:vulnerability_2) { vulnerabilities.create!(vulnerability_params.merge(state: 3)) } - - describe '#perform' do - it 'changes state of vulnerability to dismissed' do - subject.perform(vulnerability_1.id, vulnerability_2.id) - - expect(vulnerability_1.reload.state).to eq(2) - expect(vulnerability_2.reload.state).to eq(2) - end - - it 'populates missing dismissal information' do - expect_next_instance_of(::Gitlab::BackgroundMigration::PopulateMissingVulnerabilityDismissalInformation) do |migration| - expect(migration).to receive(:perform).with(vulnerability_1.id, vulnerability_2.id) - end - - subject.perform(vulnerability_1.id, vulnerability_2.id) - end - end -end diff --git a/spec/lib/gitlab/background_migration/populate_has_vulnerabilities_spec.rb b/spec/lib/gitlab/background_migration/populate_has_vulnerabilities_spec.rb deleted file mode 100644 index 6722321d5f7..00000000000 --- a/spec/lib/gitlab/background_migration/populate_has_vulnerabilities_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateHasVulnerabilities, schema: 20181228175414 do - let(:users) { table(:users) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:project_settings) { table(:project_settings) } - let(:vulnerabilities) { table(:vulnerabilities) } - - let(:user) { users.create!(name: 'test', email: 'test@example.com', projects_limit: 5) } - let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } - let(:vulnerability_base_params) { { title: 'title', state: 2, severity: 0, confidence: 5, report_type: 2, author_id: user.id } } - - let!(:project_1) { projects.create!(namespace_id: namespace.id, name: 'foo_1') } - let!(:project_2) { projects.create!(namespace_id: namespace.id, name: 'foo_2') } - let!(:project_3) { projects.create!(namespace_id: namespace.id, name: 'foo_3') } - - before do - project_settings.create!(project_id: project_1.id) - vulnerabilities.create!(vulnerability_base_params.merge(project_id: project_1.id)) - vulnerabilities.create!(vulnerability_base_params.merge(project_id: project_3.id)) - - allow(::Gitlab::BackgroundMigration::Logger).to receive_messages(info: true, error: true) - end - - describe '#perform' do - it 'sets `has_vulnerabilities` attribute of project_settings' do - expect { subject.perform(project_1.id, project_3.id) }.to change { project_settings.count }.from(1).to(2) - .and change { project_settings.where(has_vulnerabilities: true).count }.from(0).to(2) - end - - it 'writes info log message' do - subject.perform(project_1.id, project_3.id) - - expect(::Gitlab::BackgroundMigration::Logger).to have_received(:info).with(migrator: described_class.name, - message: 'Projects has been processed to populate `has_vulnerabilities` information', - count: 2) - end - - context 'when non-existing project_id is given' do - it 'populates only for the existing projects' do - expect { subject.perform(project_1.id, 0, project_3.id) }.to change { project_settings.count }.from(1).to(2) - .and change { project_settings.where(has_vulnerabilities: true).count }.from(0).to(2) - end - end - - context 'when an error happens' do - before do - allow(described_class::ProjectSetting).to receive(:upsert_for).and_raise('foo') - end - - it 'writes error log message' do - subject.perform(project_1.id, project_3.id) - - expect(::Gitlab::BackgroundMigration::Logger).to have_received(:error).with(migrator: described_class.name, - message: 'foo', - project_ids: [project_1.id, project_3.id]) - end - end - end -end diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb deleted file mode 100644 index 1d8eed53553..00000000000 --- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, schema: 20181228175414 do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:users) { table(:users) } - - let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } - let(:user_2) { users.create!(email: 'test2@example.com', projects_limit: 100, username: 'test') } - let(:user_3) { users.create!(email: 'test3@example.com', projects_limit: 100, username: 'test') } - - let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } - let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') } - let(:merge_requests) { table(:merge_requests) } - let(:merge_request_assignees) { table(:merge_request_assignees) } - - def create_merge_request(id, params = {}) - params.merge!(id: id, - target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: 'mr name', - title: "mr name#{id}") - - merge_requests.create!(params) - end - - before do - create_merge_request(2, assignee_id: user.id) - create_merge_request(3, assignee_id: user_2.id) - create_merge_request(4, assignee_id: user_3.id) - - # Test filtering MRs without assignees - create_merge_request(5, assignee_id: nil) - # Test filtering already migrated row - merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id) - end - - describe '#perform' do - it 'creates merge_request_assignees rows according to merge_requests' do - subject.perform(1, 4) - - rows = merge_request_assignees.order(:id).map { |row| row.attributes.slice('merge_request_id', 'user_id') } - existing_rows = [ - { 'merge_request_id' => 2, 'user_id' => user_3.id } - ] - created_rows = [ - { 'merge_request_id' => 3, 'user_id' => user_2.id }, - { 'merge_request_id' => 4, 'user_id' => user_3.id } - ] - expected_rows = existing_rows + created_rows - - expect(rows.size).to eq(expected_rows.size) - expected_rows.each do |expected_row| - expect(rows).to include(expected_row) - end - end - end - - describe '#perform_all_sync' do - it 'executes peform for all merge requests in batches' do - expect(subject).to receive(:perform).with(2, 4).ordered - expect(subject).to receive(:perform).with(5, 5).ordered - - subject.perform_all_sync(batch_size: 3) - end - end -end diff --git a/spec/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information_spec.rb b/spec/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information_spec.rb deleted file mode 100644 index 1c987d3876f..00000000000 --- a/spec/lib/gitlab/background_migration/populate_missing_vulnerability_dismissal_information_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateMissingVulnerabilityDismissalInformation, schema: 20181228175414 do - let(:users) { table(:users) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:vulnerabilities) { table(:vulnerabilities) } - let(:findings) { table(:vulnerability_occurrences) } - let(:scanners) { table(:vulnerability_scanners) } - let(:identifiers) { table(:vulnerability_identifiers) } - let(:feedback) { table(:vulnerability_feedback) } - - let(:user) { users.create!(name: 'test', email: 'test@example.com', projects_limit: 5) } - let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } - let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') } - let(:vulnerability_1) { vulnerabilities.create!(title: 'title', state: 2, severity: 0, confidence: 5, report_type: 2, project_id: project.id, author_id: user.id) } - let(:vulnerability_2) { vulnerabilities.create!(title: 'title', state: 2, severity: 0, confidence: 5, report_type: 2, project_id: project.id, author_id: user.id) } - let(:scanner) { scanners.create!(project_id: project.id, external_id: 'foo', name: 'bar') } - let(:identifier) { identifiers.create!(project_id: project.id, fingerprint: 'foo', external_type: 'bar', external_id: 'zoo', name: 'identifier') } - - before do - feedback.create!(feedback_type: 0, - category: 'sast', - project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1fa8', - project_id: project.id, - author_id: user.id, - created_at: Time.current) - - findings.create!(name: 'Finding', - report_type: 'sast', - project_fingerprint: Gitlab::Database::ShaAttribute.new.serialize('418291a26024a1445b23fe64de9380cdcdfd1fa8'), - location_fingerprint: 'bar', - severity: 1, - confidence: 1, - metadata_version: 1, - raw_metadata: '', - uuid: SecureRandom.uuid, - project_id: project.id, - vulnerability_id: vulnerability_1.id, - scanner_id: scanner.id, - primary_identifier_id: identifier.id) - - allow(::Gitlab::BackgroundMigration::Logger).to receive_messages(info: true, warn: true, error: true) - end - - describe '#perform' do - it 'updates the missing dismissal information of the vulnerability' do - expect { subject.perform(vulnerability_1.id, vulnerability_2.id) }.to change { vulnerability_1.reload.dismissed_at }.from(nil) - .and change { vulnerability_1.reload.dismissed_by_id }.from(nil).to(user.id) - end - - it 'writes log messages' do - subject.perform(vulnerability_1.id, vulnerability_2.id) - - expect(::Gitlab::BackgroundMigration::Logger).to have_received(:info).with(migrator: described_class.name, - message: 'Dismissal information has been copied', - count: 2) - expect(::Gitlab::BackgroundMigration::Logger).to have_received(:warn).with(migrator: described_class.name, - message: 'Could not update vulnerability!', - vulnerability_id: vulnerability_2.id) - end - end -end diff --git a/spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb b/spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb deleted file mode 100644 index f9628849dbf..00000000000 --- a/spec/lib/gitlab/background_migration/populate_personal_snippet_statistics_spec.rb +++ /dev/null @@ -1,141 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulatePersonalSnippetStatistics do - let(:file_name) { 'file_name.rb' } - let(:content) { 'content' } - let(:snippets) { table(:snippets) } - let(:snippet_repositories) { table(:snippet_repositories) } - let(:users) { table(:users) } - let(:namespaces) { table(:namespaces) } - let(:snippet_statistics) { table(:snippet_statistics) } - let(:namespace_statistics) { table(:namespace_root_storage_statistics) } - let(:routes) { table(:routes) } - let(:repo_size) { 123456 } - let(:expected_repo_size) { repo_size.megabytes } - - let(:user1) { users.create!(id: 1, email: 'test@example.com', projects_limit: 100, username: 'test1') } - let(:user2) { users.create!(id: 2, email: 'test2@example.com', projects_limit: 100, username: 'test2') } - let!(:user1_namespace) { namespaces.create!(id: 1, name: 'user1', path: 'user1', owner_id: user1.id) } - let!(:user2_namespace) { namespaces.create!(id: 2, name: 'user2', path: 'user2', owner_id: user2.id) } - let(:user1_namespace_statistics) { namespace_statistics.find_by(namespace_id: user1_namespace.id) } - let(:user2_namespace_statistics) { namespace_statistics.find_by(namespace_id: user2_namespace.id) } - - let(:ids) { snippets.pluck(:id) } - let(:migration) { described_class.new } - - subject do - migration.perform(ids) - end - - before do - allow_any_instance_of(Repository).to receive(:size).and_return(repo_size) - end - - after do - snippets.all.each { |s| raw_repository(s).remove } - end - - context 'with existing personal snippets' do - let!(:snippet1) { create_snippet(1, user1) } - let!(:snippet2) { create_snippet(2, user1) } - let!(:snippet3) { create_snippet(3, user2) } - let!(:snippet4) { create_snippet(4, user2) } - - before do - create_snippet_statistics(2, 0) - create_snippet_statistics(4, 123) - end - - it 'creates/updates all snippet_statistics' do - expect { subject }.to change { snippet_statistics.count }.from(2).to(4) - - expect(snippet_statistics.pluck(:repository_size)).to be_all(expected_repo_size) - end - - it 'creates/updates the associated namespace statistics' do - expect(migration).to receive(:update_namespace_statistics).twice.and_call_original - - subject - - stats = snippet_statistics.where(snippet_id: [snippet1, snippet2]).sum(:repository_size) - expect(user1_namespace_statistics.snippets_size).to eq stats - - stats = snippet_statistics.where(snippet_id: [snippet3, snippet4]).sum(:repository_size) - expect(user2_namespace_statistics.snippets_size).to eq stats - end - - context 'when an error is raised when updating a namespace statistics' do - it 'logs the error and continue execution' do - expect_next_instance_of(Namespaces::StatisticsRefresherService) do |instance| - expect(instance).to receive(:execute).with(Namespace.find(user1_namespace.id)).and_raise('Error') - end - - expect_next_instance_of(Namespaces::StatisticsRefresherService) do |instance| - expect(instance).to receive(:execute).and_call_original - end - - expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance| - expect(instance).to receive(:error).with(message: /Error updating statistics for namespace/).once - end - - subject - - expect(user1_namespace_statistics).to be_nil - - stats = snippet_statistics.where(snippet_id: [snippet3, snippet4]).sum(:repository_size) - expect(user2_namespace_statistics.snippets_size).to eq stats - end - end - end - - context 'when a snippet repository is empty' do - let!(:snippet1) { create_snippet(1, user1, with_repo: false) } - let!(:snippet2) { create_snippet(2, user1) } - - it 'logs error and continues execution' do - expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance| - expect(instance).to receive(:error).with(message: /Invalid snippet repository/).once - end - - subject - - expect(snippet_statistics.find_by(snippet_id: snippet1.id)).to be_nil - expect(user1_namespace_statistics.snippets_size).to eq expected_repo_size - end - end - - def create_snippet(id, author, with_repo: true) - snippets.create!(id: id, type: 'PersonalSnippet', author_id: author.id, file_name: file_name, content: content).tap do |snippet| - if with_repo - allow(snippet).to receive(:disk_path).and_return(disk_path(snippet)) - - raw_repository(snippet).create_repository - - TestEnv.copy_repo(snippet, - bare_repo: TestEnv.factory_repo_path_bare, - refs: TestEnv::BRANCH_SHA) - end - end - end - - def create_snippet_statistics(snippet_id, repository_size = 0) - snippet_statistics.create!(snippet_id: snippet_id, repository_size: repository_size) - end - - def raw_repository(snippet) - Gitlab::Git::Repository.new('default', - "#{disk_path(snippet)}.git", - Gitlab::GlRepository::SNIPPET.identifier_for_container(snippet), - "@snippets/#{snippet.id}") - end - - def hashed_repository(snippet) - Storage::Hashed.new(snippet, prefix: '@snippets') - end - - def disk_path(snippet) - hashed_repository(snippet).disk_path - end -end diff --git a/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb b/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb deleted file mode 100644 index 7884e0d97c0..00000000000 --- a/spec/lib/gitlab/background_migration/populate_project_snippet_statistics_spec.rb +++ /dev/null @@ -1,224 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::PopulateProjectSnippetStatistics do - let(:file_name) { 'file_name.rb' } - let(:content) { 'content' } - let(:snippets) { table(:snippets) } - let(:snippet_repositories) { table(:snippet_repositories) } - let(:users) { table(:users) } - let(:namespaces) { table(:namespaces) } - let(:snippet_statistics) { table(:snippet_statistics) } - let(:project_statistics) { table(:project_statistics) } - let(:projects) { table(:projects) } - let(:namespace_statistics) { table(:namespace_root_storage_statistics) } - let(:routes) { table(:routes) } - let(:repo_size) { 123456 } - let(:expected_repo_size) { repo_size.megabytes } - - let(:user) { users.create!(id: 1, email: 'test@example.com', projects_limit: 100, username: 'test') } - let(:group) { namespaces.create!(id: 10, type: 'Group', name: 'group1', path: 'group1') } - let(:user_namespace) { namespaces.create!(id: 20, name: 'user', path: 'user', owner_id: user.id) } - - let(:project1) { create_project(1, 'test', group) } - let(:project2) { create_project(2, 'test1', user_namespace) } - let(:project3) { create_project(3, 'test2', group) } - - let!(:project_stats1) { create_project_statistics(project1) } - let!(:project_stats2) { create_project_statistics(project2) } - let!(:project_stats3) { create_project_statistics(project3) } - - let(:ids) { snippets.pluck(:id) } - let(:migration) { described_class.new } - - subject do - migration.perform(ids) - - project_stats1.reload if project_stats1.persisted? - project_stats2.reload if project_stats2.persisted? - project_stats3.reload if project_stats3.persisted? - end - - before do - allow_any_instance_of(Repository).to receive(:size).and_return(repo_size) - end - - after do - snippets.all.each { |s| raw_repository(s).remove } - end - - context 'with existing user and group snippets' do - let!(:snippet1) { create_snippet(1, project1) } - let!(:snippet2) { create_snippet(2, project1) } - let!(:snippet3) { create_snippet(3, project2) } - let!(:snippet4) { create_snippet(4, project2) } - let!(:snippet5) { create_snippet(5, project3) } - - before do - create_snippet_statistics(2, 0) - create_snippet_statistics(4, 123) - end - - it 'creates/updates all snippet_statistics' do - expect(snippet_statistics.count).to eq 2 - - subject - - expect(snippet_statistics.count).to eq 5 - - snippet_statistics.all.each do |stat| - expect(stat.repository_size).to eq expected_repo_size - end - end - - it 'updates associated snippet project statistics' do - expect(project_stats1.snippets_size).to be_nil - expect(project_stats2.snippets_size).to be_nil - - subject - - snippets_size = snippet_statistics.where(snippet_id: [snippet1.id, snippet2.id]).sum(:repository_size) - expect(project_stats1.snippets_size).to eq snippets_size - - snippets_size = snippet_statistics.where(snippet_id: [snippet3.id, snippet4.id]).sum(:repository_size) - expect(project_stats2.snippets_size).to eq snippets_size - - snippets_size = snippet_statistics.where(snippet_id: snippet5.id).sum(:repository_size) - expect(project_stats3.snippets_size).to eq snippets_size - end - - it 'forces the project statistics refresh' do - expect(migration).to receive(:update_project_statistics).exactly(3).times - - subject - end - - it 'creates/updates the associated namespace statistics' do - expect(migration).to receive(:update_namespace_statistics).twice.and_call_original - - subject - - expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size + project_stats3.snippets_size - expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size - end - - context 'when the project statistics does not exists' do - it 'does not raise any error' do - project_stats3.delete - - subject - - expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size - expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size - end - end - - context 'when an error is raised when updating a project statistics' do - it 'logs the error and continue execution' do - expect(migration).to receive(:update_project_statistics).with(Project.find(project1.id)).and_raise('Error') - expect(migration).to receive(:update_project_statistics).with(Project.find(project2.id)).and_call_original - expect(migration).to receive(:update_project_statistics).with(Project.find(project3.id)).and_call_original - - expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance| - expect(instance).to receive(:error).with(message: /Error updating statistics for project #{project1.id}/).once - end - - subject - - expect(project_stats2.snippets_size).not_to be_nil - expect(project_stats3.snippets_size).not_to be_nil - end - end - - context 'when an error is raised when updating a namespace statistics' do - it 'logs the error and continue execution' do - expect(migration).to receive(:update_namespace_statistics).with(Group.find(group.id)).and_raise('Error') - expect(migration).to receive(:update_namespace_statistics).with(Namespace.find(user_namespace.id)).and_call_original - - expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance| - expect(instance).to receive(:error).with(message: /Error updating statistics for namespace/).once - end - - subject - - expect(namespace_statistics.find_by(namespace_id: user_namespace.id).snippets_size).to eq project_stats2.snippets_size - end - end - end - - context 'when project snippet is in a subgroup' do - let(:subgroup) { namespaces.create!(id: 30, type: 'Group', name: 'subgroup', path: 'subgroup', parent_id: group.id) } - let(:project1) { create_project(1, 'test', subgroup, "#{group.path}/#{subgroup.path}/test") } - let!(:snippet1) { create_snippet(1, project1) } - - it 'updates the root namespace statistics' do - subject - - expect(snippet_statistics.count).to eq 1 - expect(project_stats1.snippets_size).to eq snippet_statistics.first.repository_size - expect(namespace_statistics.find_by(namespace_id: subgroup.id)).to be_nil - expect(namespace_statistics.find_by(namespace_id: group.id).snippets_size).to eq project_stats1.snippets_size - end - end - - context 'when a snippet repository is empty' do - let!(:snippet1) { create_snippet(1, project1, with_repo: false) } - let!(:snippet2) { create_snippet(2, project1) } - - it 'logs error and continues execution' do - expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |instance| - expect(instance).to receive(:error).with(message: /Invalid snippet repository/).once - end - - subject - - expect(snippet_statistics.find_by(snippet_id: snippet1.id)).to be_nil - expect(project_stats1.snippets_size).to eq snippet_statistics.find(snippet2.id).repository_size - end - end - - def create_snippet(id, project, with_repo: true) - snippets.create!(id: id, type: 'ProjectSnippet', project_id: project.id, author_id: user.id, file_name: file_name, content: content).tap do |snippet| - if with_repo - allow(snippet).to receive(:disk_path).and_return(disk_path(snippet)) - - raw_repository(snippet).create_repository - - TestEnv.copy_repo(snippet, - bare_repo: TestEnv.factory_repo_path_bare, - refs: TestEnv::BRANCH_SHA) - end - end - end - - def create_project(id, name, namespace, path = nil) - projects.create!(id: id, name: name, path: name.downcase.gsub(/\s/, '_'), namespace_id: namespace.id).tap do |project| - path ||= "#{namespace.path}/#{project.path}" - routes.create!(id: id, source_type: 'Project', source_id: project.id, path: path) - end - end - - def create_snippet_statistics(snippet_id, repository_size = 0) - snippet_statistics.create!(snippet_id: snippet_id, repository_size: repository_size) - end - - def create_project_statistics(project, snippets_size = nil) - project_statistics.create!(id: project.id, project_id: project.id, namespace_id: project.namespace_id, snippets_size: snippets_size) - end - - def raw_repository(snippet) - Gitlab::Git::Repository.new('default', - "#{disk_path(snippet)}.git", - Gitlab::GlRepository::SNIPPET.identifier_for_container(snippet), - "@snippets/#{snippet.id}") - end - - def hashed_repository(snippet) - Storage::Hashed.new(snippet, prefix: '@snippets') - end - - def disk_path(snippet) - hashed_repository(snippet).disk_path - end -end diff --git a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb b/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb deleted file mode 100644 index 1830a7fc099..00000000000 --- a/spec/lib/gitlab/background_migration/recalculate_project_authorizations_with_min_max_user_id_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::RecalculateProjectAuthorizationsWithMinMaxUserId, schema: 20181228175414 do - let(:users_table) { table(:users) } - let(:min) { 1 } - let(:max) { 5 } - - before do - min.upto(max) do |i| - users_table.create!(id: i, email: "user#{i}@example.com", projects_limit: 10) - end - end - - describe '#perform' do - it 'initializes Users::RefreshAuthorizedProjectsService with correct users' do - min.upto(max) do |i| - user = User.find(i) - expect(Users::RefreshAuthorizedProjectsService).to( - receive(:new).with(user, any_args).and_call_original) - end - - described_class.new.perform(min, max) - end - - it 'executes Users::RefreshAuthorizedProjectsService' do - expected_call_counts = max - min + 1 - - service = instance_double(Users::RefreshAuthorizedProjectsService) - expect(Users::RefreshAuthorizedProjectsService).to( - receive(:new).exactly(expected_call_counts).times.and_return(service)) - expect(service).to receive(:execute).exactly(expected_call_counts).times - - described_class.new.perform(min, max) - end - end -end diff --git a/spec/lib/gitlab/background_migration/replace_blocked_by_links_spec.rb b/spec/lib/gitlab/background_migration/replace_blocked_by_links_spec.rb deleted file mode 100644 index 6cfdbb5a14e..00000000000 --- a/spec/lib/gitlab/background_migration/replace_blocked_by_links_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::ReplaceBlockedByLinks, schema: 20181228175414 do - let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab-org') } - let(:project) { table(:projects).create!(namespace_id: namespace.id, name: 'gitlab') } - let(:issue1) { table(:issues).create!(project_id: project.id, title: 'a') } - let(:issue2) { table(:issues).create!(project_id: project.id, title: 'b') } - let(:issue3) { table(:issues).create!(project_id: project.id, title: 'c') } - let(:issue_links) { table(:issue_links) } - let!(:blocked_link1) { issue_links.create!(source_id: issue2.id, target_id: issue1.id, link_type: 2) } - let!(:opposite_link1) { issue_links.create!(source_id: issue1.id, target_id: issue2.id, link_type: 1) } - let!(:blocked_link2) { issue_links.create!(source_id: issue1.id, target_id: issue3.id, link_type: 2) } - let!(:opposite_link2) { issue_links.create!(source_id: issue3.id, target_id: issue1.id, link_type: 0) } - let!(:nochange_link) { issue_links.create!(source_id: issue2.id, target_id: issue3.id, link_type: 1) } - - subject { described_class.new.perform(issue_links.minimum(:id), issue_links.maximum(:id)) } - - it 'deletes any opposite relations' do - subject - - expect(issue_links.ids).to match_array([nochange_link.id, blocked_link1.id, blocked_link2.id]) - end - - it 'ignores issue links other than blocked_by' do - subject - - expect(nochange_link.reload.link_type).to eq(1) - end - - it 'updates blocked_by issue links' do - subject - - expect(blocked_link1.reload.link_type).to eq(1) - expect(blocked_link1.source_id).to eq(issue1.id) - expect(blocked_link1.target_id).to eq(issue2.id) - expect(blocked_link2.reload.link_type).to eq(1) - expect(blocked_link2.source_id).to eq(issue3.id) - expect(blocked_link2.target_id).to eq(issue1.id) - end -end diff --git a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb b/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb deleted file mode 100644 index 2f5074649c4..00000000000 --- a/spec/lib/gitlab/background_migration/reset_merge_status_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::ResetMergeStatus do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') } - let(:project) { projects.create!(namespace_id: namespace.id, name: 'foo') } - let(:merge_requests) { table(:merge_requests) } - - def create_merge_request(id, extra_params = {}) - params = { - id: id, - target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: 'mr name', - title: "mr name#{id}" - }.merge(extra_params) - - merge_requests.create!(params) - end - - it 'correctly updates opened mergeable MRs to unchecked' do - create_merge_request(1, state_id: MergeRequest.available_states[:opened], merge_status: 'can_be_merged') - create_merge_request(2, state_id: MergeRequest.available_states[:opened], merge_status: 'can_be_merged') - create_merge_request(3, state_id: MergeRequest.available_states[:opened], merge_status: 'can_be_merged') - create_merge_request(4, state_id: MergeRequest.available_states[:merged], merge_status: 'can_be_merged') - create_merge_request(5, state_id: MergeRequest.available_states[:opened], merge_status: 'cannot_be_merged') - - subject.perform(1, 5) - - expected_rows = [ - { id: 1, state_id: MergeRequest.available_states[:opened], merge_status: 'unchecked' }, - { id: 2, state_id: MergeRequest.available_states[:opened], merge_status: 'unchecked' }, - { id: 3, state_id: MergeRequest.available_states[:opened], merge_status: 'unchecked' }, - { id: 4, state_id: MergeRequest.available_states[:merged], merge_status: 'can_be_merged' }, - { id: 5, state_id: MergeRequest.available_states[:opened], merge_status: 'cannot_be_merged' } - ] - - rows = merge_requests.order(:id).map do |row| - row.attributes.slice('id', 'state_id', 'merge_status').symbolize_keys - end - - expect(rows).to eq(expected_rows) - end -end diff --git a/spec/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects_spec.rb b/spec/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects_spec.rb deleted file mode 100644 index ef90b5674f0..00000000000 --- a/spec/lib/gitlab/background_migration/reset_shared_runners_for_transferred_projects_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::ResetSharedRunnersForTransferredProjects, schema: 20181228175414 do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - - let(:namespace_1) { namespaces.create!(name: 'foo', path: 'foo', shared_runners_enabled: true, allow_descendants_override_disabled_shared_runners: false ) } - let(:namespace_2) { namespaces.create!(name: 'foo', path: 'foo', shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: false ) } - let(:namespace_3) { namespaces.create!(name: 'bar', path: 'bar', shared_runners_enabled: false, allow_descendants_override_disabled_shared_runners: true ) } - let(:project_1_1) { projects.create!(namespace_id: namespace_1.id, shared_runners_enabled: true) } - let(:project_1_2) { projects.create!(namespace_id: namespace_1.id, shared_runners_enabled: false) } - let(:project_2_1) { projects.create!(namespace_id: namespace_2.id, shared_runners_enabled: true) } - let(:project_2_2) { projects.create!(namespace_id: namespace_2.id, shared_runners_enabled: false) } - let(:project_3_1) { projects.create!(namespace_id: namespace_3.id, shared_runners_enabled: true) } - let(:project_3_2) { projects.create!(namespace_id: namespace_3.id, shared_runners_enabled: false) } - - it 'corrects each project shared_runners_enabled column' do - expect do - described_class.new.perform(namespace_1.id, namespace_3.id) - project_1_1.reload - project_1_2.reload - project_2_1.reload - project_2_2.reload - project_3_1.reload - project_3_2.reload - end.to not_change(project_1_1, :shared_runners_enabled).from(true) - .and not_change(project_1_2, :shared_runners_enabled).from(false) - .and change(project_2_1, :shared_runners_enabled).from(true).to(false) - .and not_change(project_2_2, :shared_runners_enabled).from(false) - .and not_change(project_3_1, :shared_runners_enabled).from(true) - .and not_change(project_3_2, :shared_runners_enabled).from(false) - end -end diff --git a/spec/lib/gitlab/background_migration/set_merge_request_diff_files_count_spec.rb b/spec/lib/gitlab/background_migration/set_merge_request_diff_files_count_spec.rb deleted file mode 100644 index 1fdbdf25706..00000000000 --- a/spec/lib/gitlab/background_migration/set_merge_request_diff_files_count_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::SetMergeRequestDiffFilesCount, schema: 20181228175414 do - let(:merge_request_diff_files) { table(:merge_request_diff_files) } - let(:merge_request_diffs) { table(:merge_request_diffs) } - let(:merge_requests) { table(:merge_requests) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - - let(:namespace) { namespaces.create!(name: 'foo', path: 'foo') } - let(:project) { projects.create!(namespace_id: namespace.id) } - let(:merge_request) { merge_requests.create!(source_branch: 'x', target_branch: 'master', target_project_id: project.id) } - - let!(:empty_diff) { merge_request_diffs.create!(merge_request_id: merge_request.id) } - let!(:filled_diff) { merge_request_diffs.create!(merge_request_id: merge_request.id) } - - let!(:filled_diff_files) do - 1.upto(3).map do |n| - merge_request_diff_files.create!( - merge_request_diff_id: filled_diff.id, - relative_order: n, - new_file: false, - renamed_file: false, - deleted_file: false, - too_large: false, - a_mode: '', - b_mode: '', - old_path: '', - new_path: '' - ) - end - end - - it 'fills the files_count column' do - described_class.new.perform(empty_diff.id, filled_diff.id) - - expect(empty_diff.reload.files_count).to eq(0) - expect(filled_diff.reload.files_count).to eq(3) - end - - it 'uses the sentinel value if the actual count is too high' do - stub_const("#{described_class}::FILES_COUNT_SENTINEL", filled_diff_files.size - 1) - - described_class.new.perform(empty_diff.id, filled_diff.id) - - expect(empty_diff.reload.files_count).to eq(0) - expect(filled_diff.reload.files_count).to eq(described_class::FILES_COUNT_SENTINEL) - end -end diff --git a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb b/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb deleted file mode 100644 index de9799c3642..00000000000 --- a/spec/lib/gitlab/background_migration/update_existing_subgroup_to_match_visibility_level_of_parent_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::UpdateExistingSubgroupToMatchVisibilityLevelOfParent, schema: 20181228175414 do - include MigrationHelpers::NamespacesHelpers - - context 'private visibility level' do - it 'updates the project visibility' do - parent = create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE) - child = create_namespace('child', Gitlab::VisibilityLevel::PUBLIC, parent_id: parent.id) - - expect { subject.perform([parent.id], Gitlab::VisibilityLevel::PRIVATE) }.to change { child.reload.visibility_level }.to(Gitlab::VisibilityLevel::PRIVATE) - end - - it 'updates sub-sub groups' do - parent = create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE) - middle_group = create_namespace('middle', Gitlab::VisibilityLevel::PRIVATE, parent_id: parent.id) - child = create_namespace('child', Gitlab::VisibilityLevel::PUBLIC, parent_id: middle_group.id) - - subject.perform([parent.id, middle_group.id], Gitlab::VisibilityLevel::PRIVATE) - - expect(child.reload.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - - it 'updates all sub groups' do - parent = create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE) - middle_group = create_namespace('middle', Gitlab::VisibilityLevel::PUBLIC, parent_id: parent.id) - child = create_namespace('child', Gitlab::VisibilityLevel::PUBLIC, parent_id: middle_group.id) - - subject.perform([parent.id], Gitlab::VisibilityLevel::PRIVATE) - - expect(child.reload.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) - expect(middle_group.reload.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) - end - end - - context 'internal visibility level' do - it 'updates the project visibility' do - parent = create_namespace('parent', Gitlab::VisibilityLevel::INTERNAL) - child = create_namespace('child', Gitlab::VisibilityLevel::PUBLIC, parent_id: parent.id) - - expect { subject.perform([parent.id], Gitlab::VisibilityLevel::INTERNAL) }.to change { child.reload.visibility_level }.to(Gitlab::VisibilityLevel::INTERNAL) - end - end -end diff --git a/spec/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth_spec.rb b/spec/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth_spec.rb deleted file mode 100644 index 33f5e38100e..00000000000 --- a/spec/lib/gitlab/background_migration/update_existing_users_that_require_two_factor_auth_spec.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::BackgroundMigration::UpdateExistingUsersThatRequireTwoFactorAuth, schema: 20181228175414 do - include MigrationHelpers::NamespacesHelpers - - let(:group_with_2fa_parent) { create_namespace('parent', Gitlab::VisibilityLevel::PRIVATE) } - let(:group_with_2fa_child) { create_namespace('child', Gitlab::VisibilityLevel::PRIVATE, parent_id: group_with_2fa_parent.id) } - let(:members_table) { table(:members) } - let(:users_table) { table(:users) } - - subject { described_class.new } - - describe '#perform' do - context 'with group members' do - let(:user_1) { create_user('user@example.com') } - let!(:member) { create_group_member(user_1, group_with_2fa_parent) } - let!(:user_without_group) { create_user('user_without@example.com') } - let(:user_other) { create_user('user_other@example.com') } - let!(:member_other) { create_group_member(user_other, group_with_2fa_parent) } - - it 'updates user when user should not be required to establish two factor authentication' do - subject.perform(user_1.id, user_without_group.id) - - expect(user_1.reload.require_two_factor_authentication_from_group).to eq(false) - end - - it 'does not update user when user is member of group that requires two factor authentication' do - group = create_namespace('other', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true) - create_group_member(user_1, group) - - subject.perform(user_1.id, user_without_group.id) - - expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true) - end - - it 'does not update user who is not in current batch' do - subject.perform(user_1.id, user_without_group.id) - - expect(user_other.reload.require_two_factor_authentication_from_group).to eq(true) - end - - it 'updates all users in current batch' do - subject.perform(user_1.id, user_other.id) - - expect(user_other.reload.require_two_factor_authentication_from_group).to eq(false) - end - - it 'does not update user when user is member of group which parent group requires two factor authentication' do - group_with_2fa_parent.update!(require_two_factor_authentication: true) - subject.perform(user_1.id, user_other.id) - - expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true) - end - - it 'does not update user when user is member of group which has subgroup that requires two factor authentication' do - create_namespace('subgroup', Gitlab::VisibilityLevel::PRIVATE, require_two_factor_authentication: true, parent_id: group_with_2fa_child.id) - - subject.perform(user_1.id, user_other.id) - - expect(user_1.reload.require_two_factor_authentication_from_group).to eq(true) - end - end - end - - def create_user(email, require_2fa: true) - users_table.create!(email: email, projects_limit: 10, require_two_factor_authentication_from_group: require_2fa) - end - - def create_group_member(user, group) - members_table.create!(user_id: user.id, source_id: group.id, access_level: GroupMember::MAINTAINER, source_type: "Namespace", type: "GroupMember", notification_level: 3) - end -end diff --git a/spec/lib/sidebars/concerns/link_with_html_options_spec.rb b/spec/lib/sidebars/concerns/link_with_html_options_spec.rb new file mode 100644 index 00000000000..1e890bffad1 --- /dev/null +++ b/spec/lib/sidebars/concerns/link_with_html_options_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Sidebars::Concerns::LinkWithHtmlOptions do + let(:options) { {} } + + subject { Class.new { include Sidebars::Concerns::LinkWithHtmlOptions }.new } + + before do + allow(subject).to receive(:container_html_options).and_return(options) + end + + describe '#link_html_options' do + context 'with existing classes' do + let(:options) do + { + class: '_class1_ _class2_', + aria: { label: '_label_' } + } + end + + it 'includes class and default aria-label attribute' do + result = { + class: '_class1_ _class2_ gl-link', + aria: { label: '_label_' } + } + + expect(subject.link_html_options).to eq(result) + end + end + + context 'without existing classes' do + it 'includes gl-link class' do + expect(subject.link_html_options).to eq(class: 'gl-link') + end + end + end +end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index a519bdab0c7..441cd7067e5 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -5,7 +5,8 @@ require 'spec_helper' RSpec.describe Issues::CreateService do include AfterNextHelpers - let_it_be_with_reload(:project) { create(:project) } + let_it_be(:group) { create(:group) } + let_it_be_with_reload(:project) { create(:project, group: group) } let_it_be(:user) { create(:user) } let(:spam_params) { double } @@ -430,25 +431,29 @@ RSpec.describe Issues::CreateService do end context 'Quick actions' do - context 'with assignee and milestone in params and command' do + context 'with assignee, milestone, and contact in params and command' do + let_it_be(:contact) { create(:contact, group: group) } + let(:opts) do { assignee_ids: [create(:user).id], milestone_id: 1, title: 'Title', - description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}") + description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}"), + add_contacts: [contact.email] } end before_all do - project.add_maintainer(user) + group.add_maintainer(user) project.add_maintainer(assignee) end - it 'assigns and sets milestone to issuable from command' do + it 'assigns, sets milestone, and sets contact to issuable from command' do expect(issue).to be_persisted expect(issue.assignees).to eq([assignee]) expect(issue.milestone).to eq(milestone) + expect(issue.issue_customer_relations_contacts.last.contact).to eq(contact) end end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index b5246505558..4739b7e0f28 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -29,6 +29,8 @@ RSpec.describe Issues::UpdateService, :mailer do end describe 'execute' do + let_it_be(:contact) { create(:contact, group: group) } + def find_note(starting_with) issue.notes.find do |note| note && note.note.start_with?(starting_with) @@ -57,7 +59,8 @@ RSpec.describe Issues::UpdateService, :mailer do due_date: Date.tomorrow, discussion_locked: true, severity: 'low', - milestone_id: milestone.id + milestone_id: milestone.id, + add_contacts: [contact.email] } end @@ -76,6 +79,7 @@ RSpec.describe Issues::UpdateService, :mailer do expect(issue.discussion_locked).to be_truthy expect(issue.confidential).to be_falsey expect(issue.milestone).to eq milestone + expect(issue.issue_customer_relations_contacts.last.contact).to eq contact end it 'updates issue milestone when passing `milestone` param' do diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 2bdc29a09a0..77d263f4b70 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -2253,35 +2253,29 @@ RSpec.describe QuickActions::InterpretService do end it 'add_contacts command does not add the contact' do - add_command + _, updates, _ = add_command - expect(issue.reload.customer_relations_contacts).to match_array([existing_contact]) + expect(updates).to be_empty end it 'remove_contacts command does not remove the contact' do - remove_command + _, updates, _ = remove_command - expect(issue.reload.customer_relations_contacts).to match_array([existing_contact]) + expect(updates).to be_empty end end it 'add_contacts command adds the contact' do - _, _, message = add_command + _, updates, message = add_command - expect(issue.reload.customer_relations_contacts).to match_array([existing_contact, new_contact]) + expect(updates).to eq(add_contacts: [new_contact.email]) expect(message).to eq('One or more contacts were successfully added.') end - it 'add_contacts command returns the correct error when something goes wrong' do - _, _, message = service.execute("/add_contacts #{new_contact.email} #{new_contact.email} #{new_contact.email} #{new_contact.email} #{new_contact.email} #{new_contact.email} #{new_contact.email}", issue) - - expect(message).to eq('You can only add up to 6 contacts at one time') - end - it 'remove_contacts command removes the contact' do - _, _, message = remove_command + _, updates, message = remove_command - expect(issue.reload.customer_relations_contacts).to be_empty + expect(updates).to eq(remove_contacts: [existing_contact.email]) expect(message).to eq('One or more contacts were successfully removed.') end end diff --git a/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb index 07012914a4d..6414a4d1eb3 100644 --- a/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb +++ b/spec/support/shared_contexts/features/integrations/project_integrations_shared_context.rb @@ -28,7 +28,7 @@ RSpec.shared_context 'project service activation' do end def click_test_integration - click_link('Test settings') + click_button('Test settings') end def click_test_then_save_integration(expect_test_to_fail: true) |