summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-08-08 18:11:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-08-08 18:11:24 +0000
commit70b375c29f67bdc8bd7e8ade1d5355444106482d (patch)
tree7e4fdec178464a016953a9aecfd349441edb9f44
parent3de2ce7c6b536d63ea2f93239022eb51fa9241c1 (diff)
downloadgitlab-ce-70b375c29f67bdc8bd7e8ade1d5355444106482d.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS1
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml4
-rw-r--r--.gitlab/merge_request_templates/Documentation.md2
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue47
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue203
-rw-r--r--app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue6
-rw-r--r--app/assets/javascripts/ci_variable_list/constants.js52
-rw-r--r--app/assets/javascripts/ci_variable_list/store/utils.js6
-rw-r--r--app/assets/javascripts/ci_variable_list/utils.js45
-rw-r--r--app/assets/javascripts/labels/labels_select.js2
-rw-r--r--app/assets/javascripts/projects/project_new.js24
-rw-r--r--app/controllers/search_controller.rb17
-rw-r--r--app/views/projects/_new_project_fields.html.haml18
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/pages/_use.html.haml15
-rw-r--r--app/views/projects/project_templates/_template.html.haml3
-rw-r--r--app/workers/gitlab/github_import/stage/import_issue_events_worker.rb28
-rw-r--r--config/feature_flags/ops/github_importer_issue_events_import.yml2
-rw-r--r--config/feature_flags/ops/github_importer_single_endpoint_issue_events_import.yml (renamed from config/feature_flags/development/custom_headers_streaming_audit_events_ui.yml)12
-rw-r--r--config/routes.rb4
-rw-r--r--db/migrate/20220723120039_add_author_id_to_vulnerability_state_transitions.rb7
-rw-r--r--db/post_migrate/20220726182310_add_user_fk_to_vulnerability_state_transitions.rb20
-rw-r--r--db/schema_migrations/202207231200391
-rw-r--r--db/schema_migrations/202207261823101
-rw-r--r--db/structure.sql8
-rw-r--r--doc/administration/audit_event_streaming.md275
-rw-r--r--doc/administration/audit_events.md2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md4
-rw-r--r--doc/development/distributed_tracing.md16
-rw-r--r--doc/development/logging.md15
-rw-r--r--doc/user/usage_quotas.md19
-rw-r--r--lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml2
-rw-r--r--lib/gitlab/github_import/importer/events/base_importer.rb10
-rw-r--r--lib/gitlab/github_import/importer/events/changed_assignee.rb2
-rw-r--r--lib/gitlab/github_import/importer/events/changed_label.rb2
-rw-r--r--lib/gitlab/github_import/importer/events/changed_milestone.rb2
-rw-r--r--lib/gitlab/github_import/importer/events/closed.rb4
-rw-r--r--lib/gitlab/github_import/importer/events/cross_referenced.rb4
-rw-r--r--lib/gitlab/github_import/importer/events/renamed.rb2
-rw-r--r--lib/gitlab/github_import/importer/events/reopened.rb4
-rw-r--r--lib/gitlab/github_import/importer/issue_event_importer.rb48
-rw-r--r--lib/gitlab/github_import/importer/issue_events_importer.rb35
-rw-r--r--lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb5
-rw-r--r--lib/gitlab/github_import/issuable_finder.rb2
-rw-r--r--lib/gitlab/github_import/representation/issue_event.rb13
-rw-r--r--locale/gitlab.pot8
-rw-r--r--qa/qa/resource/project.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb5
-rw-r--r--qa/qa/support/parallel_pipeline_jobs.rb74
-rw-r--r--qa/tasks/knapsack.rake25
-rw-r--r--spec/controllers/search_controller_spec.rb49
-rw-r--r--spec/features/projects/new_project_spec.rb32
-rw-r--r--spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js139
-rw-r--r--spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js380
-rw-r--r--spec/frontend/ci_variable_list/mocks.js116
-rw-r--r--spec/frontend/ci_variable_list/utils_spec.js68
-rw-r--r--spec/frontend/content_editor/remark_markdown_processing_spec.js2
-rw-r--r--spec/frontend/labels/labels_select_spec.js6
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js14
-rw-r--r--spec/frontend/projects/project_new_spec.js67
-rw-r--r--spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js12
-rw-r--r--spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb14
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb13
-rw-r--r--spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb13
-rw-r--r--spec/lib/gitlab/github_import/importer/events/closed_spec.rb12
-rw-r--r--spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb31
-rw-r--r--spec/lib/gitlab/github_import/importer/events/renamed_spec.rb12
-rw-r--r--spec/lib/gitlab/github_import/importer/events/reopened_spec.rb12
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb122
-rw-r--r--spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/issuable_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/representation/issue_event_spec.rb30
-rw-r--r--spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb20
-rw-r--r--spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb24
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb53
79 files changed, 1817 insertions, 554 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 7810021a883..8e3a753b1d8 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1049,7 +1049,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/app/services/audit_event_service.rb @gitlab-org/manage/compliance
/app/services/concerns/audit_event_save_type.rb @gitlab-org/manage/compliance
/app/views/profiles/audit_log.html.haml @gitlab-org/manage/compliance
-/config/feature_flags/development/custom_headers_streaming_audit_events_ui.yml @gitlab-org/manage/compliance
/data/deprecations/14-3-repository-push-audit-events.yml @gitlab-org/manage/compliance
/data/removals/15_0/removal_manage_repository_push_audit_event.yml @gitlab-org/manage/compliance
/db/docs/audit_events.yml @gitlab-org/manage/compliance
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index 8c48e803ad3..631fe7fef30 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
@@ -88,10 +88,8 @@ download-knapsack-report:
- .bundle-base
- .review:rules:review-qa-reliable
stage: prepare
- variables:
- QA_KNAPSACK_REPORTS: review-qa-reliable,review-qa-all
script:
- - bundle exec rake "knapsack:download"
+ - bundle exec rake "knapsack:download[qa]"
allow_failure: true
artifacts:
paths:
diff --git a/.gitlab/merge_request_templates/Documentation.md b/.gitlab/merge_request_templates/Documentation.md
index cdc33b8aacb..4932fbd7f26 100644
--- a/.gitlab/merge_request_templates/Documentation.md
+++ b/.gitlab/merge_request_templates/Documentation.md
@@ -43,5 +43,5 @@ Documentation-related MRs should be reviewed by a Technical Writer for a non-blo
- If the content still needs to be edited for topic types, you can create a follow-up issue with the ~"docs-technical-debt" label.
- [ ] Review by assigned maintainer, who can always request/require the reviews above. Maintainer's review can occur before or after a technical writer review.
-/label ~documentation ~"type::maintenance" ~"docs::improvement"
+/label ~documentation ~"type::maintenance" ~"docs::improvement" ~"maintenance::refactor"
/assign me
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue b/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue
index ecb39f214ec..8ee7132bb25 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_environments_dropdown.vue
@@ -1,7 +1,7 @@
<script>
import { GlDropdown, GlDropdownItem, GlDropdownDivider, GlSearchBoxByType } from '@gitlab/ui';
-import { mapGetters } from 'vuex';
import { __, sprintf } from '~/locale';
+import { convertEnvironmentScope } from '../utils';
export default {
name: 'CiEnvironmentsDropdown',
@@ -12,7 +12,11 @@ export default {
GlSearchBoxByType,
},
props: {
- value: {
+ environments: {
+ type: Array,
+ required: true,
+ },
+ selectedEnvironmentScope: {
type: String,
required: false,
default: '',
@@ -24,31 +28,36 @@ export default {
};
},
computed: {
- ...mapGetters(['joinedEnvironments']),
composedCreateButtonLabel() {
return sprintf(__('Create wildcard: %{searchTerm}'), { searchTerm: this.searchTerm });
},
- shouldRenderCreateButton() {
- return this.searchTerm && !this.joinedEnvironments.includes(this.searchTerm);
- },
- filteredResults() {
+ filteredEnvironments() {
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
- return this.joinedEnvironments.filter((resultString) =>
+ return this.environments.filter((resultString) =>
resultString.toLowerCase().includes(lowerCasedSearchTerm),
);
},
+ shouldRenderCreateButton() {
+ return this.searchTerm && !this.environments.includes(this.searchTerm);
+ },
+ environmentScopeLabel() {
+ return convertEnvironmentScope(this.selectedEnvironmentScope);
+ },
},
methods: {
selectEnvironment(selected) {
- this.$emit('selectEnvironment', selected);
- this.searchTerm = '';
+ this.$emit('select-environment', selected);
+ this.clearSearch();
},
- createClicked() {
- this.$emit('createClicked', this.searchTerm);
- this.searchTerm = '';
+ convertEnvironmentScopeValue(scope) {
+ return convertEnvironmentScope(scope);
+ },
+ createEnvironmentScope() {
+ this.$emit('create-environment-scope', this.searchTerm);
+ this.selectEnvironment(this.searchTerm);
},
isSelected(env) {
- return this.value === env;
+ return this.selectedEnvironmentScope === env;
},
clearSearch() {
this.searchTerm = '';
@@ -57,23 +66,23 @@ export default {
};
</script>
<template>
- <gl-dropdown :text="value" @show="clearSearch">
+ <gl-dropdown :text="environmentScopeLabel" @show="clearSearch">
<gl-search-box-by-type v-model.trim="searchTerm" data-testid="ci-environment-search" />
<gl-dropdown-item
- v-for="environment in filteredResults"
+ v-for="environment in filteredEnvironments"
:key="environment"
:is-checked="isSelected(environment)"
is-check-item
@click="selectEnvironment(environment)"
>
- {{ environment }}
+ {{ convertEnvironmentScopeValue(environment) }}
</gl-dropdown-item>
- <gl-dropdown-item v-if="!filteredResults.length" ref="noMatchingResults">{{
+ <gl-dropdown-item v-if="!filteredEnvironments.length" ref="noMatchingResults">{{
__('No matching results')
}}</gl-dropdown-item>
<template v-if="shouldRenderCreateButton">
<gl-dropdown-divider />
- <gl-dropdown-item data-testid="create-wildcard-button" @click="createClicked">
+ <gl-dropdown-item data-testid="create-wildcard-button" @click="createEnvironmentScope">
{{ composedCreateButtonLabel }}
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
index 557a8d6b5ba..dc57f3fe4ce 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
@@ -14,22 +14,26 @@ import {
GlModal,
GlSprintf,
} from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
import { getCookie, setCookie } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import Tracking from '~/tracking';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { mapComputed } from '~/vuex_shared/bindings';
+
import {
+ allEnvironments,
AWS_TOKEN_CONSTANTS,
ADD_CI_VARIABLE_MODAL_ID,
AWS_TIP_DISMISSED_COOKIE_NAME,
AWS_TIP_MESSAGE,
CONTAINS_VARIABLE_REFERENCE_MESSAGE,
+ defaultVariableState,
ENVIRONMENT_SCOPE_LINK_TITLE,
EVENT_LABEL,
EVENT_ACTION,
+ EDIT_VARIABLE_ACTION,
+ VARIABLE_ACTIONS,
+ variableOptions,
} from '../constants';
+
import CiEnvironmentsDropdown from './ci_environments_dropdown.vue';
import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens';
@@ -58,66 +62,75 @@ export default {
GlModal,
GlSprintf,
},
- mixins: [glFeatureFlagsMixin(), trackingMixin],
+ mixins: [trackingMixin],
+ inject: [
+ 'awsLogoSvgPath',
+ 'awsTipCommandsLink',
+ 'awsTipDeployLink',
+ 'awsTipLearnLink',
+ 'containsVariableReferenceLink',
+ 'environmentScopeLink',
+ 'isProtectedByDefault',
+ 'maskedEnvironmentVariablesLink',
+ 'maskableRegex',
+ 'protectedEnvironmentVariablesLink',
+ ],
+ props: {
+ areScopedVariablesAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ environments: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ mode: {
+ type: String,
+ required: true,
+ validator(val) {
+ return VARIABLE_ACTIONS.includes(val);
+ },
+ },
+ selectedVariable: {
+ type: Object,
+ required: false,
+ default: () => {},
+ },
+ },
data() {
return {
isTipDismissed: getCookie(AWS_TIP_DISMISSED_COOKIE_NAME) === 'true',
+ typeOptions: variableOptions,
validationErrorEventProperty: '',
+ variable: { ...defaultVariableState, ...this.selectedVariable },
};
},
computed: {
- ...mapState([
- 'projectId',
- 'environments',
- 'typeOptions',
- 'variable',
- 'variableBeingEdited',
- 'isGroup',
- 'maskableRegex',
- 'selectedEnvironment',
- 'isProtectedByDefault',
- 'awsLogoSvgPath',
- 'awsTipDeployLink',
- 'awsTipCommandsLink',
- 'awsTipLearnLink',
- 'containsVariableReferenceLink',
- 'protectedEnvironmentVariablesLink',
- 'maskedEnvironmentVariablesLink',
- 'environmentScopeLink',
- ]),
- ...mapComputed(
- [
- { key: 'key', updateFn: 'updateVariableKey' },
- { key: 'secret_value', updateFn: 'updateVariableValue' },
- { key: 'variable_type', updateFn: 'updateVariableType' },
- { key: 'environment_scope', updateFn: 'setEnvironmentScope' },
- { key: 'protected_variable', updateFn: 'updateVariableProtected' },
- { key: 'masked', updateFn: 'updateVariableMasked' },
- ],
- false,
- 'variable',
- ),
- isTipVisible() {
- return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key);
- },
- canSubmit() {
- return (
- this.variableValidationState &&
- this.variable.key !== '' &&
- this.variable.secret_value !== ''
- );
- },
canMask() {
const regex = RegExp(this.maskableRegex);
- return regex.test(this.variable.secret_value);
+ return regex.test(this.variable.value);
+ },
+ canSubmit() {
+ return this.variableValidationState && this.variable.key !== '' && this.variable.value !== '';
},
containsVariableReference() {
const regex = /\$/;
- return regex.test(this.variable.secret_value);
+ return regex.test(this.variable.value);
},
displayMaskedError() {
return !this.canMask && this.variable.masked;
},
+ isEditing() {
+ return this.mode === EDIT_VARIABLE_ACTION;
+ },
+ isTipVisible() {
+ return !this.isTipDismissed && AWS_TOKEN_CONSTANTS.includes(this.variable.key);
+ },
+ maskedFeedback() {
+ return this.displayMaskedError ? __('This variable can not be masked.') : '';
+ },
maskedState() {
if (this.displayMaskedError) {
return false;
@@ -125,10 +138,7 @@ export default {
return true;
},
modalActionText() {
- return this.variableBeingEdited ? __('Update variable') : __('Add variable');
- },
- maskedFeedback() {
- return this.displayMaskedError ? __('This variable can not be masked.') : '';
+ return this.isEditing ? __('Update variable') : __('Add variable');
},
tokenValidationFeedback() {
const tokenSpecificFeedback = this.$options.tokens?.[this.variable.key]?.invalidMessage;
@@ -141,19 +151,16 @@ export default {
const validator = this.$options.tokens?.[this.variable.key]?.validation;
if (validator) {
- return validator(this.variable.secret_value);
+ return validator(this.variable.value);
}
return true;
},
- scopedVariablesAvailable() {
- return !this.isGroup || this.glFeatures.groupScopedCiVariables;
- },
variableValidationFeedback() {
return `${this.tokenValidationFeedback} ${this.maskedFeedback}`;
},
variableValidationState() {
- return this.variable.secret_value === '' || (this.tokenValidationState && this.maskedState);
+ return this.variable.value === '' || (this.tokenValidationState && this.maskedState);
},
},
watch: {
@@ -165,19 +172,18 @@ export default {
},
},
methods: {
- ...mapActions([
- 'addVariable',
- 'updateVariable',
- 'resetEditing',
- 'displayInputValue',
- 'clearModal',
- 'deleteVariable',
- 'setEnvironmentScope',
- 'addWildCardScope',
- 'resetSelectedEnvironment',
- 'setSelectedEnvironment',
- 'setVariableProtected',
- ]),
+ addVariable() {
+ this.$emit('add-variable', this.variable);
+ },
+ createEnvironmentScope(env) {
+ this.$emit('create-environment-scope', env);
+ },
+ deleteVariable() {
+ this.$emit('delete-variable', this.variable);
+ },
+ updateVariable() {
+ this.$emit('update-variable', this.variable);
+ },
dismissTip() {
setCookie(AWS_TIP_DISMISSED_COOKIE_NAME, 'true', { expires: 90 });
this.isTipDismissed = true;
@@ -190,16 +196,22 @@ export default {
this.$refs.modal.hide();
},
resetModalHandler() {
- if (this.variableBeingEdited) {
- this.resetEditing();
- }
-
- this.clearModal();
- this.resetSelectedEnvironment();
+ this.resetVariableData();
this.resetValidationErrorEvents();
+
+ this.$emit('hideModal');
+ },
+ resetVariableData() {
+ this.variable = { ...defaultVariableState };
+ },
+ setEnvironmentScope(scope) {
+ this.variable = { ...this.variable, environmentScope: scope };
+ },
+ setVariableProtected() {
+ this.variable = { ...this.variable, protected: true };
},
updateOrAddVariable() {
- if (this.variableBeingEdited) {
+ if (this.isEditing) {
this.updateVariable();
} else {
this.addVariable();
@@ -207,7 +219,7 @@ export default {
this.hideModal();
},
setVariableProtectedByDefault() {
- if (this.isProtectedByDefault && !this.variableBeingEdited) {
+ if (this.isProtectedByDefault && !this.isEditing) {
this.setVariableProtected();
}
},
@@ -220,11 +232,11 @@ export default {
},
getTrackingErrorProperty() {
let property;
- if (this.variable.secret_value?.length && !property) {
+ if (this.variable.value?.length && !property) {
if (this.displayMaskedError && this.maskableRegex?.length) {
const supportedChars = this.maskableRegex.replace('^', '').replace(/{(\d,)}\$/, '');
const regex = new RegExp(supportedChars, 'g');
- property = this.variable.secret_value.replace(regex, '');
+ property = this.variable.value.replace(regex, '');
}
if (this.containsVariableReference) {
property = '$';
@@ -237,6 +249,7 @@ export default {
this.validationErrorEventProperty = '';
},
},
+ defaultScope: allEnvironments.text,
};
</script>
@@ -252,7 +265,7 @@ export default {
>
<form>
<gl-form-combobox
- v-model="key"
+ v-model="variable.key"
:token-list="$options.tokenList"
:label-text="__('Key')"
data-qa-selector="ci_variable_key_field"
@@ -267,7 +280,7 @@ export default {
<gl-form-textarea
id="ci-variable-value"
ref="valueField"
- v-model="secret_value"
+ v-model="variable.value"
:state="variableValidationState"
rows="3"
max-rows="6"
@@ -278,7 +291,11 @@ export default {
<div class="d-flex">
<gl-form-group :label="__('Type')" label-for="ci-variable-type" class="w-50 gl-mr-5">
- <gl-form-select id="ci-variable-type" v-model="variable_type" :options="typeOptions" />
+ <gl-form-select
+ id="ci-variable-type"
+ v-model="variable.variableType"
+ :options="typeOptions"
+ />
</gl-form-group>
<gl-form-group label-for="ci-variable-env" class="w-50" data-testid="environment-scope">
@@ -294,22 +311,24 @@ export default {
</gl-link>
</template>
<ci-environments-dropdown
- v-if="scopedVariablesAvailable"
- class="w-100"
- :value="environment_scope"
- @selectEnvironment="setEnvironmentScope"
- @createClicked="addWildCardScope"
+ v-if="areScopedVariablesAvailable"
+ class="gl-w-full"
+ :selected-environment-scope="variable.environmentScope"
+ :environments="environments"
+ @select-environment="setEnvironmentScope"
+ @create-environment-scope="createEnvironmentScope"
/>
- <gl-form-input v-else v-model="environment_scope" class="w-100" readonly />
+ <gl-form-input v-else :value="$options.defaultScope" class="gl-w-full" readonly />
</gl-form-group>
</div>
<gl-form-group :label="__('Flags')" label-for="ci-variable-flags">
<gl-form-checkbox
- v-model="protected_variable"
- class="mb-0"
+ v-model="variable.protected"
+ class="gl-mb-0"
data-testid="ci-variable-protected-checkbox"
+ :data-is-protected-checked="variable.protected"
>
{{ __('Protect variable') }}
<gl-link target="_blank" :href="protectedEnvironmentVariablesLink">
@@ -322,7 +341,7 @@ export default {
<gl-form-checkbox
ref="masked-ci-variable"
- v-model="masked"
+ v-model="variable.masked"
data-testid="ci-variable-masked-checkbox"
>
{{ __('Mask variable') }}
@@ -403,7 +422,7 @@ export default {
<template #modal-footer>
<gl-button @click="hideModal">{{ __('Cancel') }}</gl-button>
<gl-button
- v-if="variableBeingEdited"
+ v-if="isEditing"
ref="deleteCiVariable"
variant="danger"
category="secondary"
diff --git a/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue
index 7dcc5ce42d7..cebb7eb85ac 100644
--- a/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue
+++ b/app/assets/javascripts/ci_variable_list/components/legacy_ci_variable_modal.vue
@@ -30,7 +30,7 @@ import {
EVENT_LABEL,
EVENT_ACTION,
} from '../constants';
-import CiEnvironmentsDropdown from './ci_environments_dropdown.vue';
+import LegacyCiEnvironmentsDropdown from './legacy_ci_environments_dropdown.vue';
import { awsTokens, awsTokenList } from './ci_variable_autocomplete_tokens';
const trackingMixin = Tracking.mixin({ label: EVENT_LABEL });
@@ -43,7 +43,7 @@ export default {
containsVariableReferenceMessage: CONTAINS_VARIABLE_REFERENCE_MESSAGE,
environmentScopeLinkTitle: ENVIRONMENT_SCOPE_LINK_TITLE,
components: {
- CiEnvironmentsDropdown,
+ LegacyCiEnvironmentsDropdown,
GlAlert,
GlButton,
GlCollapse,
@@ -293,7 +293,7 @@ export default {
<gl-icon name="question" :size="12" />
</gl-link>
</template>
- <ci-environments-dropdown
+ <legacy-ci-environments-dropdown
v-if="scopedVariablesAvailable"
class="w-100"
:value="environment_scope"
diff --git a/app/assets/javascripts/ci_variable_list/constants.js b/app/assets/javascripts/ci_variable_list/constants.js
index fa55b4d9e77..e42a728a44e 100644
--- a/app/assets/javascripts/ci_variable_list/constants.js
+++ b/app/assets/javascripts/ci_variable_list/constants.js
@@ -2,16 +2,49 @@ import { __ } from '~/locale';
export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable';
+// This const will be deprecated once we remove VueX from the section
export const displayText = {
variableText: __('Variable'),
fileText: __('File'),
allEnvironmentsText: __('All (default)'),
};
+export const variableTypes = {
+ variableType: 'ENV_VAR',
+ fileType: 'FILE',
+};
+
+// Once REST is removed, we won't need `types`
export const types = {
variableType: 'env_var',
fileType: 'file',
- allEnvironmentsType: '*',
+};
+
+export const allEnvironments = {
+ type: '*',
+ text: __('All (default)'),
+};
+
+// Once REST is removed, we won't need `types` key
+export const variableText = {
+ [types.variableType]: __('Variable'),
+ [types.fileType]: __('File'),
+ [variableTypes.variableType]: __('Variable'),
+ [variableTypes.fileType]: __('File'),
+};
+
+export const variableOptions = [
+ { value: types.variableType, text: variableText[types.variableType] },
+ { value: types.fileType, text: variableText[types.fileType] },
+];
+
+export const defaultVariableState = {
+ environmentScope: allEnvironments.type,
+ key: '',
+ masked: false,
+ protected: false,
+ value: '',
+ variableType: types.variableType,
};
export const AWS_TIP_DISMISSED_COOKIE_NAME = 'ci_variable_list_constants_aws_tip_dismissed';
@@ -33,3 +66,20 @@ export const CONTAINS_VARIABLE_REFERENCE_MESSAGE = __(
);
export const ENVIRONMENT_SCOPE_LINK_TITLE = __('Learn more');
+
+export const ADD_VARIABLE_ACTION = 'ADD_VARIABLE';
+export const EDIT_VARIABLE_ACTION = 'EDIT_VARIABLE';
+export const VARIABLE_ACTIONS = [ADD_VARIABLE_ACTION, EDIT_VARIABLE_ACTION];
+
+export const GRAPHQL_PROJECT_TYPE = 'Project';
+export const GRAPHQL_GROUP_TYPE = 'Group';
+
+export const ADD_MUTATION_ACTION = 'add';
+export const UPDATE_MUTATION_ACTION = 'update';
+export const DELETE_MUTATION_ACTION = 'delete';
+
+export const environmentFetchErrorText = __(
+ 'There was an error fetching the environments information.',
+);
+export const genericMutationErrorText = __('Something went wrong on our end. Please try again.');
+export const variableFetchErrorText = __('There was an error fetching the variables.');
diff --git a/app/assets/javascripts/ci_variable_list/store/utils.js b/app/assets/javascripts/ci_variable_list/store/utils.js
index d9ca460a8e1..f46a671ae7b 100644
--- a/app/assets/javascripts/ci_variable_list/store/utils.js
+++ b/app/assets/javascripts/ci_variable_list/store/utils.js
@@ -1,5 +1,5 @@
import { cloneDeep } from 'lodash';
-import { displayText, types } from '../constants';
+import { displayText, types, allEnvironments } from '../constants';
const variableTypeHandler = (type) =>
type === displayText.variableText ? types.variableType : types.fileType;
@@ -15,7 +15,7 @@ export const prepareDataForDisplay = (variables) => {
}
variableCopy.secret_value = variableCopy.value;
- if (variableCopy.environment_scope === types.allEnvironmentsType) {
+ if (variableCopy.environment_scope === allEnvironments.type) {
variableCopy.environment_scope = displayText.allEnvironmentsText;
}
variableCopy.protected_variable = variableCopy.protected;
@@ -31,7 +31,7 @@ export const prepareDataForApi = (variable, destroy = false) => {
variableCopy.masked = variableCopy.masked.toString();
variableCopy.variable_type = variableTypeHandler(variableCopy.variable_type);
if (variableCopy.environment_scope === displayText.allEnvironmentsText) {
- variableCopy.environment_scope = types.allEnvironmentsType;
+ variableCopy.environment_scope = allEnvironments.type;
}
if (destroy) {
diff --git a/app/assets/javascripts/ci_variable_list/utils.js b/app/assets/javascripts/ci_variable_list/utils.js
new file mode 100644
index 00000000000..009e56469d1
--- /dev/null
+++ b/app/assets/javascripts/ci_variable_list/utils.js
@@ -0,0 +1,45 @@
+import { uniq } from 'lodash';
+import { allEnvironments } from './constants';
+
+/**
+ * This function takes aa list of variable and environments
+ * and create a new Array that concatenate the environment list
+ * with the environment scopes find in the variable list. This is
+ * useful for variable settings so that we can render a list of all
+ * environment scopes available based on both the list of envs and what
+ * is found under each variable.
+ * @param {Array} variables
+ * @param {Array} environments
+ * @returns {Array} - Array of environments
+ */
+
+export const createJoinedEnvironments = (variables = [], environments = []) => {
+ const scopesFromVariables = variables.map((variable) => variable.environmentScope);
+ return uniq(environments.concat(scopesFromVariables)).sort();
+};
+
+/**
+ * This function job is to convert the * wildcard to text when applicable
+ * in the UI. It uses a constants to compare the incoming value to that
+ * of the * and then apply the corresponding label if applicable. If there
+ * is no scope, then we return the default value as well.
+ * @param {String} scope
+ * @returns {String} - Converted value if applicable
+ */
+
+export const convertEnvironmentScope = (environmentScope = '') => {
+ if (environmentScope === allEnvironments.type || !environmentScope) {
+ return allEnvironments.text;
+ }
+
+ return environmentScope;
+};
+
+/**
+ * Gives us an array of all the environments by name
+ * @param {Array} nodes
+ * @return {Array<String>} - Array of environments strings
+ */
+export const mapEnvironmentNames = (nodes = []) => {
+ return nodes.map((env) => env.name);
+};
diff --git a/app/assets/javascripts/labels/labels_select.js b/app/assets/javascripts/labels/labels_select.js
index 9d8ee165df2..3e5396c5bd8 100644
--- a/app/assets/javascripts/labels/labels_select.js
+++ b/app/assets/javascripts/labels/labels_select.js
@@ -438,7 +438,7 @@ export default class LabelsSelect {
[
'<% if (isScopedLabel(label) && enableScopedLabels) { %>',
"<span class='font-weight-bold scoped-label-tooltip-title'>Scoped label</span>",
- '<br />',
+ '<br>',
'<%= escapeStr(label.description) %>',
'<% } else { %>',
'<%= escapeStr(label.description) %>',
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index fe84660422b..424ea3b61c5 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import { debounce } from 'lodash';
import DEFAULT_PROJECT_TEMPLATES from 'any_else_ce/projects/default_project_templates';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import Tracking from '~/tracking';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '../lib/utils/constants';
import { ENTER_KEY } from '../lib/utils/keys';
import axios from '../lib/utils/axios_utils';
@@ -109,8 +110,31 @@ const setProjectNamePathHandlers = ($projectNameInput, $projectPathInput) => {
);
};
+ const projectPathValueListener = () => {
+ // eslint-disable-next-line no-param-reassign
+ $projectPathInput.oldInputValue = $projectPathInput.value;
+ };
+
+ const projectPathTrackListener = () => {
+ if ($projectPathInput.oldInputValue === $projectPathInput.value) {
+ // no change made to the input
+ return;
+ }
+
+ const trackEvent = 'user_input_path_slug';
+ const trackCategory = undefined; // will be default set in event method
+
+ Tracking.event(trackCategory, trackEvent, {
+ label: 'new_project_form',
+ });
+ };
+
$projectPathInput.removeEventListener('keyup', projectPathInputListener);
$projectPathInput.addEventListener('keyup', projectPathInputListener);
+ $projectPathInput.removeEventListener('focus', projectPathValueListener);
+ $projectPathInput.addEventListener('focus', projectPathValueListener);
+ $projectPathInput.removeEventListener('blur', projectPathTrackListener);
+ $projectPathInput.addEventListener('blur', projectPathTrackListener);
$projectPathInput.removeEventListener('change', projectPathInputListener);
$projectPathInput.addEventListener('change', projectPathInputListener);
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 5a655c92e46..5843e13c7cd 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -7,10 +7,14 @@ class SearchController < ApplicationController
include ProductAnalyticsTracking
include SearchRateLimitable
- RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show, :autocomplete].freeze
+ RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show, :autocomplete, :aggregations].freeze
track_event :show, name: 'i_search_total', destinations: [:redis_hll, :snowplow]
+ def self.search_rate_limited_endpoints
+ %i[show count autocomplete]
+ end
+
around_action :allow_gitaly_ref_name_caching
before_action :block_anonymous_global_searches, :check_scope_global_search_enabled, except: :opensearch
@@ -19,7 +23,7 @@ class SearchController < ApplicationController
search_term_present = params[:search].present? || params[:term].present?
search_term_present && !params[:project_id].present?
end
- before_action :check_search_rate_limit!, only: [:show, :count, :autocomplete]
+ before_action :check_search_rate_limit!, only: search_rate_limited_endpoints
rescue_from ActiveRecord::QueryCanceled, with: :render_timeout
@@ -32,8 +36,6 @@ class SearchController < ApplicationController
@project = search_service.project
@group = search_service.group
- return if params[:search].blank?
-
return unless search_term_valid?
return if check_single_commit_result?
@@ -53,7 +55,6 @@ class SearchController < ApplicationController
@search_results = @search_service.search_results
@search_objects = @search_service.search_objects
@search_highlight = @search_service.search_highlight
- @aggregations = @search_service.search_aggregations
end
increment_search_counters
@@ -99,6 +100,8 @@ class SearchController < ApplicationController
end
def search_term_valid?
+ return false if params[:search].blank?
+
unless search_service.valid_query_length?
flash[:alert] = t('errors.messages.search_chars_too_long', count: Gitlab::Search::Params::SEARCH_CHAR_LIMIT)
return false
@@ -151,7 +154,7 @@ class SearchController < ApplicationController
payload[:metadata]['meta.search.filters.state'] = params[:state]
payload[:metadata]['meta.search.force_search_results'] = params[:force_search_results]
payload[:metadata]['meta.search.project_ids'] = params[:project_ids]
- payload[:metadata]['meta.search.language'] = params[:language]
+ payload[:metadata]['meta.search.filters.language'] = params[:language]
payload[:metadata]['meta.search.type'] = @search_type if @search_type.present?
payload[:metadata]['meta.search.level'] = @search_level if @search_level.present?
payload[:metadata][:global_search_duration_s] = @global_search_duration_s if @global_search_duration_s.present?
@@ -207,7 +210,7 @@ class SearchController < ApplicationController
case action_name.to_sym
when :count
render json: {}, status: :request_timeout
- when :autocomplete
+ when :autocomplete, :aggregations
render json: [], status: :request_timeout
else
render status: :request_timeout
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 3c7d358bde9..98cd831d6f1 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -1,6 +1,7 @@
- visibility_level = selected_visibility_level(@project, params.dig(:project, :visibility_level))
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
- hide_init_with_readme = local_assigns.fetch(:hide_init_with_readme, false)
+- include_description = local_assigns.fetch(:include_description, true)
- track_label = local_assigns.fetch(:track_label, 'blank_project')
.row{ id: project_name_id }
@@ -44,10 +45,19 @@
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/profile/index', anchor: 'add-details-to-your-profile-with-a-readme') }
= html_escape(_('%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}')) % { project_path: "<strong>#{current_user.username} / #{current_user.username}</strong>".html_safe, help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
-.form-group
- = f.label :description, class: 'label-bold' do
- = s_('ProjectsNew|Project description %{tag_start}(optional)%{tag_end}').html_safe % { tag_start: '<span>'.html_safe, tag_end: '</span>'.html_safe }
- = f.text_area :description, placeholder: s_('ProjectsNew|Description format'), class: "form-control gl-form-input", rows: 3, maxlength: 250, data: { qa_selector: 'project_description', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_description", track_value: "" }
+- if include_description
+ .form-group
+ = f.label :description, class: 'label-bold' do
+ = s_('ProjectsNew|Project description %{tag_start}(optional)%{tag_end}').html_safe % { tag_start: '<span>'.html_safe, tag_end: '</span>'.html_safe }
+ = f.text_area :description,
+ placeholder: s_('ProjectsNew|Description format'),
+ class: "form-control gl-form-input",
+ rows: 3,
+ maxlength: 250,
+ data: { qa_selector: 'project_description',
+ track_label: track_label,
+ track_action: "activate_form_input",
+ track_property: "project_description" }
- unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? || !Gitlab.com?
.js-deployment-target-select
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 07c38d9845c..56581fe7b18 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -13,7 +13,7 @@
.row{ 'v-cloak': true }
#blank-project-pane.tab-pane.active
= gitlab_ui_form_for @project, html: { class: 'new_project gl-mt-3' } do |f|
- = render 'new_project_fields', f: f, project_name_id: "blank-project-name"
+ = render 'new_project_fields', f: f, project_name_id: "blank-project-name", include_description: false
#create-from-template-pane.tab-pane
= render Pajamas::CardComponent.new(card_options: { class: 'gl-my-5' }) do |c|
diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml
index 0ab99c83fd0..dccf61c6ec5 100644
--- a/app/views/projects/pages/_use.html.haml
+++ b/app/views/projects/pages/_use.html.haml
@@ -1,10 +1,9 @@
- unless @project.pages_deployed?
- .card.border-info
- .card-header.bg-info.text-white
+ = render Pajamas::CardComponent.new(card_options: { class: 'gl-border-blue-500' }, header_options: { class: 'gl-bg-blue-500 gl-text-white' }) do |c|
+ - c.header do
= s_('GitLabPages|Configure pages')
- .card-body
- %p.gl-mb-0
- - docs_link_start = "<a href='#{help_page_path('user/project/pages/index')}' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_docs_link'>".html_safe
- - samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_samples_link'>".html_safe
- - link_end = '</a>'.html_safe
- = s_('GitLabPages|Your Pages site is not configured yet. See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also take some inspiration from the %{samples_link_start}sample Pages projects%{link_end}.').html_safe % { docs_link_start: docs_link_start, samples_link_start: samples_link_start, link_end: link_end }
+ - c.body do
+ - docs_link_start = "<a href='#{help_page_path('user/project/pages/index')}' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_docs_link'>".html_safe
+ - samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_samples_link'>".html_safe
+ - link_end = '</a>'.html_safe
+ = s_('GitLabPages|Your Pages site is not configured yet. See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also take some inspiration from the %{samples_link_start}sample Pages projects%{link_end}.').html_safe % { docs_link_start: docs_link_start, samples_link_start: samples_link_start, link_end: link_end }
diff --git a/app/views/projects/project_templates/_template.html.haml b/app/views/projects/project_templates/_template.html.haml
index 5e4b1397dd3..d0fdd3a729a 100644
--- a/app/views/projects/project_templates/_template.html.haml
+++ b/app/views/projects/project_templates/_template.html.haml
@@ -10,7 +10,8 @@
.controls.d-flex.align-items-center
%a.btn.gl-button.btn-default.gl-mr-3{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "template_preview", track_property: template.name, track_action: "click_button", track_value: "" } }
= _("Preview")
- %label.btn.gl-button.btn-confirm.template-button.choose-template.gl-mb-0{ for: template.name }
+ %label.btn.gl-button.btn-confirm.template-button.choose-template.gl-mb-0{ for: template.name,
+ 'data-testid': "use_template_#{template.name}" }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "template_use", track_property: template.name, track_action: "click_button", track_value: "" } }
%span{ data: { qa_selector: 'use_template_button' } }
= _("Use template")
diff --git a/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb b/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb
index 8155b910677..0ec0a1b58d2 100644
--- a/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb
+++ b/app/workers/gitlab/github_import/stage/import_issue_events_worker.rb
@@ -15,32 +15,34 @@ module Gitlab
# client - An instance of Gitlab::GithubImport::Client.
# project - An instance of Project.
def import(client, project)
- importer = ::Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
- return skip_to_next_stage(project, importer) if feature_disabled?(project)
+ importer = importer_class(project)
+ return skip_to_next_stage(project) if importer.nil?
- start_importer(project, importer, client)
+ info(project.id, message: "starting importer", importer: importer.name)
+ waiter = importer.new(project, client).execute
+ move_to_next_stage(project, { waiter.key => waiter.jobs_remaining })
end
private
- def start_importer(project, importer, client)
- info(project.id, message: "starting importer", importer: importer.name)
- waiter = importer.new(project, client).execute
- move_to_next_stage(project, waiter.key => waiter.jobs_remaining)
+ def importer_class(project)
+ if Feature.enabled?(:github_importer_single_endpoint_issue_events_import, project.group, type: :ops)
+ ::Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
+ elsif Feature.enabled?(:github_importer_issue_events_import, project.group, type: :ops)
+ ::Gitlab::GithubImport::Importer::IssueEventsImporter
+ else
+ nil
+ end
end
- def skip_to_next_stage(project, importer)
- info(project.id, message: "skipping importer", importer: importer.name)
+ def skip_to_next_stage(project)
+ info(project.id, message: "skipping importer", importer: "IssueEventsImporter")
move_to_next_stage(project)
end
def move_to_next_stage(project, waiters = {})
AdvanceStageWorker.perform_async(project.id, waiters, :notes)
end
-
- def feature_disabled?(project)
- Feature.disabled?(:github_importer_issue_events_import, project.group, type: :ops)
- end
end
end
end
diff --git a/config/feature_flags/ops/github_importer_issue_events_import.yml b/config/feature_flags/ops/github_importer_issue_events_import.yml
index c4710858a0a..58660ceb287 100644
--- a/config/feature_flags/ops/github_importer_issue_events_import.yml
+++ b/config/feature_flags/ops/github_importer_issue_events_import.yml
@@ -2,7 +2,7 @@
name: github_importer_issue_events_import
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89134
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365977
-milestone: '15.2'
+milestone: '15.3'
type: ops
group: group::import
default_enabled: false
diff --git a/config/feature_flags/development/custom_headers_streaming_audit_events_ui.yml b/config/feature_flags/ops/github_importer_single_endpoint_issue_events_import.yml
index 710a4f55130..88e9db6721f 100644
--- a/config/feature_flags/development/custom_headers_streaming_audit_events_ui.yml
+++ b/config/feature_flags/ops/github_importer_single_endpoint_issue_events_import.yml
@@ -1,8 +1,8 @@
---
-name: custom_headers_streaming_audit_events_ui
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90135
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365259
-milestone: '15.2'
-type: development
-group: group::compliance
+name: github_importer_single_endpoint_issue_events_import
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89134
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365977
+milestone: '15.3'
+type: ops
+group: group::import
default_enabled: false
diff --git a/config/routes.rb b/config/routes.rb
index dd3095f0a8d..859443be77c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -88,6 +88,10 @@ InitializerConnections.with_disabled_database_connections do
get 'search/count' => 'search#count', as: :search_count
get 'search/opensearch' => 'search#opensearch', as: :search_opensearch
+ Gitlab.ee do
+ get 'search/aggregations' => 'search#aggregations', as: :search_aggregations
+ end
+
# JSON Web Token
get 'jwt/auth' => 'jwt#auth'
diff --git a/db/migrate/20220723120039_add_author_id_to_vulnerability_state_transitions.rb b/db/migrate/20220723120039_add_author_id_to_vulnerability_state_transitions.rb
new file mode 100644
index 00000000000..4b16ba2bb82
--- /dev/null
+++ b/db/migrate/20220723120039_add_author_id_to_vulnerability_state_transitions.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddAuthorIdToVulnerabilityStateTransitions < Gitlab::Database::Migration[2.0]
+ def change
+ add_column :vulnerability_state_transitions, :author_id, :bigint
+ end
+end
diff --git a/db/post_migrate/20220726182310_add_user_fk_to_vulnerability_state_transitions.rb b/db/post_migrate/20220726182310_add_user_fk_to_vulnerability_state_transitions.rb
new file mode 100644
index 00000000000..ad0bf6141b2
--- /dev/null
+++ b/db/post_migrate/20220726182310_add_user_fk_to_vulnerability_state_transitions.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddUserFkToVulnerabilityStateTransitions < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_vulnerability_state_transitions_on_author_id'
+
+ def up
+ add_concurrent_index :vulnerability_state_transitions, :author_id, name: INDEX_NAME
+ add_concurrent_foreign_key :vulnerability_state_transitions, :users, column: :author_id, on_delete: :nullify
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :vulnerability_state_transitions, column: :author_id
+ end
+
+ remove_concurrent_index_by_name :vulnerability_state_transitions, INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20220723120039 b/db/schema_migrations/20220723120039
new file mode 100644
index 00000000000..54be61091e3
--- /dev/null
+++ b/db/schema_migrations/20220723120039
@@ -0,0 +1 @@
+1a6a488243a8fa564f07301028477d64ca290b4ec636cfaab4816dab8bf3dd3f \ No newline at end of file
diff --git a/db/schema_migrations/20220726182310 b/db/schema_migrations/20220726182310
new file mode 100644
index 00000000000..fd5d282f525
--- /dev/null
+++ b/db/schema_migrations/20220726182310
@@ -0,0 +1 @@
+318684106f2976e285b6aaa1a73363badeec083a180a68973ba9d51dd89886c0 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index d99366fd015..5b6e41a5315 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -22547,7 +22547,8 @@ CREATE TABLE vulnerability_state_transitions (
to_state smallint NOT NULL,
from_state smallint NOT NULL,
created_at timestamp with time zone NOT NULL,
- updated_at timestamp with time zone NOT NULL
+ updated_at timestamp with time zone NOT NULL,
+ author_id bigint
);
CREATE SEQUENCE vulnerability_state_transitions_id_seq
@@ -30326,6 +30327,8 @@ CREATE UNIQUE INDEX index_vulnerability_scanners_on_project_id_and_external_id O
CREATE INDEX index_vulnerability_state_transitions_id_and_vulnerability_id ON vulnerability_state_transitions USING btree (vulnerability_id, id);
+CREATE INDEX index_vulnerability_state_transitions_on_author_id ON vulnerability_state_transitions USING btree (author_id);
+
CREATE INDEX index_vulnerability_statistics_on_latest_pipeline_id ON vulnerability_statistics USING btree (latest_pipeline_id);
CREATE INDEX index_vulnerability_statistics_on_letter_grade ON vulnerability_statistics USING btree (letter_grade);
@@ -32654,6 +32657,9 @@ ALTER TABLE ONLY gitlab_subscriptions
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_e719a85f8a FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
+ALTER TABLE ONLY vulnerability_state_transitions
+ ADD CONSTRAINT fk_e719dc63df FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
+
ALTER TABLE ONLY issue_links
ADD CONSTRAINT fk_e71bb44f1f FOREIGN KEY (target_id) REFERENCES issues(id) ON DELETE CASCADE;
diff --git a/doc/administration/audit_event_streaming.md b/doc/administration/audit_event_streaming.md
index e3928e31962..d51fc0b2097 100644
--- a/doc/administration/audit_event_streaming.md
+++ b/doc/administration/audit_event_streaming.md
@@ -6,39 +6,53 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Audit event streaming **(ULTIMATE)**
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/332747) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `ff_external_audit_events_namespace`. Disabled by default.
-> - [Enabled on GitLab.com and by default on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/338939) in GitLab 14.7.
-> - [Feature flag `ff_external_audit_events_namespace`](https://gitlab.com/gitlab-org/gitlab/-/issues/349588) removed in GitLab 14.8.
+> - API [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/332747) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `ff_external_audit_events_namespace`. Disabled by default.
+> - API [Enabled on GitLab.com and by default on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/338939) in GitLab 14.7.
+> - API [Feature flag `ff_external_audit_events_namespace`](https://gitlab.com/gitlab-org/gitlab/-/issues/349588) removed in GitLab 14.8.
+> - UI [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336411) in GitLab 14.9.
> - [Subgroup events recording](https://gitlab.com/gitlab-org/gitlab/-/issues/366878) fixed in GitLab 15.2.
+> - Custom HTTP headers API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361216) in GitLab 15.1 [with a flag](feature_flags.md) named `streaming_audit_event_headers`. Disabled by default.
+> - Custom HTTP headers API [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/362941) in GitLab 15.2.
+> - Custom HTTP headers API [made generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/366524) in GitLab 15.3. [Feature flag `streaming_audit_event_headers`](https://gitlab.com/gitlab-org/gitlab/-/issues/362941) removed.
+> - Custom HTTP headers UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361630) in GitLab 15.2 [with a flag](feature_flags.md) named `custom_headers_streaming_audit_events_ui`. Disabled by default.
+> - Custom HTTP headers UI [made generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/365259) in GitLab 15.3. [Feature flag `custom_headers_streaming_audit_events_ui`](https://gitlab.com/gitlab-org/gitlab/-/issues/365259) removed.
-Users can set an HTTP endpoint for a top-level group to receive all audit events about the group, its subgroups, and
+Users can set a streaming destination for a top-level group to receive all audit events about the group, its subgroups, and
projects as structured JSON.
Top-level group owners can manage their audit logs in third-party systems. Any service that can receive
-structured JSON data can be used as the endpoint.
+structured JSON data can be used as the streaming destination.
+
+Each streaming destination can have up to 20 custom HTTP headers included with each streamed event.
NOTE:
GitLab can stream a single event more than once to the same destination. Use the `id` key in the payload to deduplicate incoming data.
-## Add a new event streaming destination
+## Add a new streaming destination
WARNING:
-Event streaming destinations receive **all** audit event data, which could include sensitive information. Make sure you trust the destination endpoint.
+Streaming destinations receive **all** audit event data, which could include sensitive information. Make sure you trust the streaming destination.
### Use the GitLab UI
-Users with at least the Owner role for a group can add event streaming destinations for it:
+Users with the Owner role for a group can add streaming destinations for it:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Security & Compliance > Audit events**.
1. On the main area, select **Streams** tab.
- When the destination list is empty, select **Add stream** to show the section for adding destinations.
- - When the destination list is not empty, select **{plus}** to show the section for adding destinations.
-1. Enter the destination URL to add and select **Add**.
+ - When the destination list is not empty, select **Add stream** (**{plus}**) to show the section for adding destinations.
+1. Enter the destination URL to add.
+1. Optional. Locate the **Custom HTTP headers** table.
+1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the **Active** checkbox, see the
+ [relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/361925).
+1. Enter as many name and value pairs as required. When you enter a unique name and a value for a header, a new row in the table automatically appears. You can add up to
+ 20 headers per streaming destination.
+1. After all headers have been filled out, select **Add** to add the new streaming destination.
### Use the API
-To enable event streaming and add a destination, users with at least the Owner role for a group must use the
+To enable streaming and add a destination, users with the Owner role for a group must use the
`externalAuditEventDestinationCreate` mutation in the GraphQL API.
```graphql
@@ -46,6 +60,7 @@ mutation {
externalAuditEventDestinationCreate(input: { destinationUrl: "https://mydomain.io/endpoint/ingest", groupPath: "my-group" } ) {
errors
externalAuditEventDestination {
+ id
destinationUrl
verificationToken
group {
@@ -61,23 +76,35 @@ Event streaming is enabled if:
- The returned `errors` object is empty.
- The API responds with `200 OK`.
+Group owners can add an HTTP header using the GraphQL `auditEventsStreamingHeadersCreate` mutation. You can retrieve the destination ID
+by [listing all the streaming destinations](#use-the-api-1) for the group or from the mutation above.
+
+```graphql
+mutation {
+ auditEventsStreamingHeadersCreate(input: { destinationId: "gid://gitlab/AuditEvents::ExternalAuditEventDestination/24601", key: "foo", value: "bar" }) {
+ errors
+ }
+}
+```
+
+The header is created if the returned `errors` object is empty.
+
## List streaming destinations
-Users with at least the Owner role for a group can list event streaming destinations.
+Users with the Owner role for a group can list streaming destinations.
### Use the GitLab UI
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336411) in GitLab 14.9.
-
-Users with at least the Owner role for a group can list event streaming destinations:
+To list the streaming destinations:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Security & Compliance > Audit events**.
1. On the main area, select **Streams** tab.
+1. To the right of the item, select **Edit** (**{pencil}**) to see all the custom HTTP headers.
### Use the API
-Users with at least the Owner role for a group can view a list of event streaming destinations at any time using the
+Users with the Owner role for a group can view a list of streaming destinations at any time using the
`externalAuditEventDestinations` query type.
```graphql
@@ -89,37 +116,48 @@ query {
destinationUrl
verificationToken
id
+ headers {
+ nodes {
+ key
+ value
+ id
+ }
+ }
}
}
}
}
```
-If the resulting list is empty, then audit event streaming is not enabled for that group.
+If the resulting list is empty, then audit streaming is not enabled for that group.
-## Delete streaming destinations
+You need the ID values returned by this query for the update and delete mutations.
-Users with at least the Owner role for a group can delete event streaming destinations using the
-`deleteAuditEventDestinations` mutation type.
+## Update streaming destinations
-When the last destination is successfully deleted, event streaming is disabled for the group.
+Users with the Owner role for a group can update streaming destinations.
### Use the GitLab UI
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336411) in GitLab 14.9.
-
-Users with at least the Owner role for a group can delete event streaming destinations:
+To update a streaming destinations custom HTTP headers:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Security & Compliance > Audit events**.
1. On the main area, select **Streams** tab.
-1. Select **{remove}** at the right side of each item.
+1. To the right of the item, select **Edit** (**{pencil}**).
+1. Locate the **Custom HTTP headers** table.
+1. Locate the header that you wish to update.
+1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the **Active** checkbox, see the
+ [relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/361925).
+1. Enter as many name and value pairs as required. When you enter a unique name and a value for a header, a new row in the table automatically appears. You can add up to
+ 20 headers per streaming destination.
+1. Select **Save** to update the streaming destination.
### Use the API
-Delete an event streaming destination by specifying an ID. Get the required ID by
-[listing the details](audit_event_streaming.md#use-the-api-1) of event
-streaming destinations.
+Users with the Owner role for a group can update streaming destinations custom HTTP headers using the
+`auditEventsStreamingHeadersUpdate` mutation type. You can retrieve the custom HTTP headers ID
+by [listing all the custom HTTP headers](#use-the-api-1) for the group.
```graphql
mutation {
@@ -129,112 +167,71 @@ mutation {
}
```
-Destination is deleted if:
+Streaming destination is updated if:
- The returned `errors` object is empty.
- The API responds with `200 OK`.
-## Custom HTTP headers
-
-> - API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361216) in GitLab 15.1 [with a flag](feature_flags.md) named `streaming_audit_event_headers`. Disabled by default.
-> - API [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/362941) in GitLab 15.2.
-> - UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361630) in GitLab 15.2 [with a flag](feature_flags.md) named `custom_headers_streaming_audit_events_ui`. Disabled by default.
-> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/366524) in GitLab 15.3.
-
-Each streaming destination can have up to 20 custom HTTP headers included with each streamed event.
-
-### Adding custom HTTP headers
-
-Add custom HTTP headers with the API or GitLab UI.
-
-#### Use the API
-
-Group owners can add a HTTP header using the GraphQL `auditEventsStreamingHeadersCreate` mutation. You can retrieve the destination ID
-by [listing the external audit destinations](#list-streaming-destinations) on the group.
+Group owners can remove an HTTP header using the GraphQL `auditEventsStreamingHeadersDestroy` mutation. You can retrieve the header ID
+by [listing all the custom HTTP headers](#use-the-api-1) for the group.
```graphql
mutation {
- auditEventsStreamingHeadersCreate(input: { destinationId: "gid://gitlab/AuditEvents::ExternalAuditEventDestination/24601", key: "foo", value: "bar" }) {
+ auditEventsStreamingHeadersDestroy(input: { headerId: "gid://gitlab/AuditEvents::Streaming::Header/1" }) {
errors
}
}
```
-The header is created if the returned `errors` object is empty.
+The header is deleted if the returned `errors` object is empty.
-#### Use the GitLab UI
+## Delete streaming destinations
-FLAG:
-On self-managed GitLab, by default the UI for this feature is not available. To make it available per group, ask an administrator to
-[enable the feature flag](../administration/feature_flags.md) named `custom_headers_streaming_audit_events_ui`. On GitLab.com, the UI for this feature is
-not available. The UI for this feature is not ready for production use.
+Users with the Owner role for a group can delete streaming destinations.
+
+When the last destination is successfully deleted, streaming is disabled for the group.
-Users with at least the Owner role for a group can add event streaming destinations and custom HTTP headers for it:
+### Use the GitLab UI
+
+To delete a streaming destination:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Security & Compliance > Audit events**.
-1. On the main area, select **Streams** tab.
- - When the destination list is empty, select **Add stream** to show the section for adding destinations.
- - When the destination list is not empty, select **{plus}** to show the section for adding destinations.
-1. Enter the destination URL to add.
-1. Locate the **Custom HTTP headers** table.
-1. In the **Header** column, add the header's name.
-1. In the **Value** column, add the header's value.
-1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the **Active** checkbox, see the
- [relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/361925).
-1. Enter as many name and value pairs as required. When you enter a unique name and a value for a header, a new row in the table automatically appears. You can add up to
- 20 headers per endpoint.
-1. After all headers have been filled out, select **Add** to add the new endpoint.
-
-### Updating custom HTTP headers
+1. On the main area, select the **Streams** tab.
+1. To the right of the item, select **Delete** (**{remove}**).
-Add custom HTTP headers with the API or GitLab UI.
+To delete only the custom HTTP headers for a streaming destination:
-#### Use the API
+1. On the top bar, select **Menu > Groups** and find your group.
+1. On the left sidebar, select **Security & Compliance > Audit events**.
+1. On the main area, select the **Streams** tab.
+1. To the right of the item, **Edit** (**{pencil}**).
+1. Locate the **Custom HTTP headers** table.
+1. Locate the header that you wish to remove.
+1. To the right of the header, select **Delete** (**{remove}**).
+1. Select **Save** to update the streaming destination.
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/361964) in GitLab 15.2.
+### Use the API
-Group owners can update a HTTP header using the GraphQL `auditEventsStreamingHeadersCreate` mutation.
+Users with the Owner role for a group can delete streaming destinations using the
+`externalAuditEventDestinationDestroy` mutation type. You can retrieve the destinations ID
+by [listing all the streaming destinations](#use-the-api-1) for the group.
```graphql
mutation {
- auditEventsStreamingHeadersUpdate(input: { headerId: "gid://gitlab/AuditEvents::Streaming::Header/24255", key: "new-foo", value: "new-bar" }) {
+ externalAuditEventDestinationDestroy(input: { id: destination }) {
errors
}
}
```
-#### Use the GitLab UI
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91986) in GitLab 15.3 [with a flag](feature_flags.md) named `custom_headers_streaming_audit_events_ui`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default the UI for this feature is not available. To make it available per group, ask an administrator to
-[enable the feature flag](../administration/feature_flags.md) named `custom_headers_streaming_audit_events_ui`. On GitLab.com, the UI for this feature is
-not available. The UI for this feature is not ready for production use.
-
-Users with at least the Owner role for a group can add event streaming destinations and custom HTTP headers for it:
-
-1. On the top bar, select **Menu > Groups** and find your group.
-1. On the left sidebar, select **Security & Compliance > Audit events**.
-1. On the main area, select **Streams** tab.
-1. Select **{pencil}** at the right side of an item.
-1. Locate the **Custom HTTP headers** table.
-1. Locate the header that you wish to update.
-1. In the **Header** column, you can change the header's name.
-1. In the **Value** column, you can change the header's value.
-1. Ignore the **Active** checkbox because it isn't functional. To track progress on adding functionality to the **Active** checkbox, see the
- [relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/361925).
-1. Select **Save** to update the endpoint.
-
-### Deleting custom HTTP headers
-
-Deleting custom HTTP headers with the API or GitLab UI.
+Streaming destination is deleted if:
-#### Use the API
+- The returned `errors` object is empty.
+- The API responds with `200 OK`.
-Group owners can remove a HTTP header using the GraphQL `auditEventsStreamingHeadersDestroy` mutation. You can retrieve the header ID
-by [listing all the custom headers](#list-all-custom-headers) on the group.
+Group owners can remove an HTTP header using the GraphQL `auditEventsStreamingHeadersDestroy` mutation. You can retrieve the header ID
+by [listing all the custom HTTP headers](#use-the-api-1) for the group.
```graphql
mutation {
@@ -246,70 +243,6 @@ mutation {
The header is deleted if the returned `errors` object is empty.
-#### Use the GitLab UI
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91986) in GitLab 15.3 [with a flag](feature_flags.md) named `custom_headers_streaming_audit_events_ui`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default the UI for this feature is not available. To make it available per group, ask an administrator to
-[enable the feature flag](../administration/feature_flags.md) named `custom_headers_streaming_audit_events_ui`. On GitLab.com, the UI for this feature is
-not available. The UI for this feature is not ready for production use.
-
-Users with at least the Owner role for a group can delete event streaming destinations:
-
-1. On the top bar, select **Menu > Groups** and find your group.
-1. On the left sidebar, select **Security & Compliance > Audit events**.
-1. On the main area, select **Streams** tab.
-1. Select **{pencil}** at the right side of an item.
-1. Locate the **Custom HTTP headers** table.
-1. Locate the header that you wish to remove.
-1. Select **{remove}** at the right side of the header.
-1. Select **Save** to update the endpoint.
-
-### List all custom headers
-
-List all custom HTTP headers with the API or GitLab UI.
-
-#### Use the API
-
-You can list all custom headers for a top-level group as well as their value and ID using the GraphQL `externalAuditEventDestinations` query. The ID
-value returned by this query is what you need to pass to the `deletion` mutation.
-
-```graphql
-query {
- group(fullPath: "your-group") {
- id
- externalAuditEventDestinations {
- nodes {
- destinationUrl
- id
- headers {
- nodes {
- key
- value
- id
- }
- }
- }
- }
- }
-}
-```
-
-#### Use the GitLab UI
-
-FLAG:
-On self-managed GitLab, by default the UI for this feature is not available. To make it available per group, ask an administrator to
-[enable the feature flag](../administration/feature_flags.md) named `custom_headers_streaming_audit_events_ui`. On GitLab.com, the UI for this feature is
-not available. The UI for this feature is not ready for production use.
-
-Users with at least the Owner role for a group can add event streaming destinations and custom HTTP headers for it:
-
-1. On the top bar, select **Menu > Groups** and find your group.
-1. On the left sidebar, select **Security & Compliance > Audit events**.
-1. On the main area, select **Streams** tab.
-1. Select **{pencil}** at the right side of an item.
-
## Verify event authenticity
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345424) in GitLab 14.8.
@@ -324,11 +257,11 @@ the destination's value when [listing streaming destinations](#list-streaming-de
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/360814) in GitLab 15.2.
-Users with at least the Owner role for a group can list event streaming destinations and see the verification tokens:
+Users with the Owner role for a group can list streaming destinations and see the verification tokens:
1. On the top bar, select **Menu > Groups** and find your group.
1. On the left sidebar, select **Security & Compliance > Audit events**.
-1. On the main area, select **Streams**.
+1. On the main area, select the **Streams**.
1. View the verification token on the right side of each item.
## Audit event streaming on Git operations
@@ -350,7 +283,7 @@ Streaming audit events can be sent when signed-in users push or pull a project's
Audit events are not captured for users that are not signed in. For example, when downloading a public project.
-To configure streaming audit events for Git operations, see [Add a new event streaming destination](#add-a-new-event-streaming-destination).
+To configure streaming audit events for Git operations, see [Add a new streaming destination](#add-a-new-streaming-destination).
### Headers
@@ -432,7 +365,7 @@ Push:
}
```
-#### Example payloads for SSH events with Deploy Key
+### Example payloads for SSH events with Deploy Key
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/363876) in GitLab 15.3.
@@ -534,7 +467,7 @@ Push:
}
```
-#### Example payloads for HTTP and HTTPS events with Deploy Token
+### Example payloads for HTTP and HTTPS events with Deploy Token
Fetch:
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index 3282bb5195d..0aa766aa0d4 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -272,7 +272,7 @@ Don't see the event you want in any of the epics linked above? You can either:
- Use the **Audit Event Proposal** issue template to
[create an issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new?issuable_template=Audit%20Event%20Proposal) to
request it.
-- [Add it yourself](../development/audit_event_guide/).
+- [Add it yourself](../development/audit_event_guide/index.md).
### Removed events
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 16851e5e2b5..a2def8a9f64 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -390,8 +390,8 @@ Some basic Ruby runtime metrics are available:
## Redis metrics
These client metrics are meant to complement Redis server metrics.
-These metrics are broken down per [Redis
-instance](https://docs.gitlab.com/omnibus/settings/redis.html#running-with-multiple-redis-instances).
+These metrics are broken down per
+[Redis instance](https://docs.gitlab.com/omnibus/settings/redis.html#running-with-multiple-redis-instances).
These metrics all have a `storage` label which indicates the Redis
instance (`cache`, `shared_state`, and so on).
diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md
index 116071cdfd9..f49d024095d 100644
--- a/doc/development/distributed_tracing.md
+++ b/doc/development/distributed_tracing.md
@@ -73,14 +73,14 @@ In this example, we have the following hypothetical values:
- `driver`: the driver such a Jaeger.
- `param_name`, `param_value`: these are driver specific configuration values. Configuration
- parameters for Jaeger are documented [further on in this
- document](#2-configure-the-gitlab_tracing-environment-variable) they should be URL encoded.
+ parameters for Jaeger are documented [further on in this document](#2-configure-the-gitlab_tracing-environment-variable)
+ they should be URL encoded.
Multiple values should be separated by `&` characters like a URL.
## Using Jaeger in the GitLab Development Kit
-The first tracing implementation that GitLab supports is Jaeger, and the [GitLab Development
-Kit](https://gitlab.com/gitlab-org/gitlab-development-kit/) supports distributed tracing with
+The first tracing implementation that GitLab supports is Jaeger, and the
+[GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit/) supports distributed tracing with
Jaeger out-of-the-box.
The easiest way to access tracing from a GDK environment is through the
@@ -116,8 +116,8 @@ Jaeger has many configuration options, but is very easy to start in an "all-in-o
memory for trace storage (and is therefore non-persistent). The main advantage of "all-in-one" mode
being ease of use.
-For more detailed configuration options, refer to the [Jaeger
-documentation](https://www.jaegertracing.io/docs/1.9/getting-started/).
+For more detailed configuration options, refer to the
+[Jaeger documentation](https://www.jaegertracing.io/docs/1.9/getting-started/).
#### Using Docker
@@ -201,8 +201,8 @@ If `GITLAB_TRACING` is not configured correctly, this issue is logged:
```
By default, GitLab ships with the Jaeger tracer, but other tracers can be included at compile time.
-Details of how this can be done are included in the [LabKit tracing
-documentation](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/tracing).
+Details of how this can be done are included in the
+[LabKit tracing documentation](https://pkg.go.dev/gitlab.com/gitlab-org/labkit/tracing).
If no log messages about tracing are emitted, the `GITLAB_TRACING` environment variable is likely
not set.
diff --git a/doc/development/logging.md b/doc/development/logging.md
index 7f013edee79..f1fa7f4c8c9 100644
--- a/doc/development/logging.md
+++ b/doc/development/logging.md
@@ -44,8 +44,7 @@ These logs suffer from a number of problems:
Note that currently on GitLab.com, any messages in `production.log` aren't
indexed by Elasticsearch due to the sheer volume and noise. They
do end up in Google Stackdriver, but it is still harder to search for
-logs there. See the [GitLab.com logging
-documentation](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging)
+logs there. See the [GitLab.com logging documentation](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging)
for more details.
## Use structured (JSON) logging
@@ -386,18 +385,18 @@ end
## Additional steps with new log files
1. Consider log retention settings. By default, Omnibus rotates any
- logs in `/var/log/gitlab/gitlab-rails/*.log` every hour and [keep at
- most 30 compressed files](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate).
+ logs in `/var/log/gitlab/gitlab-rails/*.log` every hour and
+ [keep at most 30 compressed files](https://docs.gitlab.com/omnibus/settings/logs.html#logrotate).
On GitLab.com, that setting is only 6 compressed files. These settings should suffice
for most users, but you may need to tweak them in [Omnibus GitLab](https://gitlab.com/gitlab-org/omnibus-gitlab).
-1. If you add a new file, submit an issue to the [production
- tracker](https://gitlab.com/gitlab-com/gl-infra/production/-/issues) or
+1. If you add a new file, submit an issue to the
+ [production tracker](https://gitlab.com/gitlab-com/gl-infra/production/-/issues) or
a merge request to the [`gitlab_fluentd`](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd)
project. See [this example](https://gitlab.com/gitlab-cookbooks/gitlab_fluentd/-/merge_requests/51/diffs).
-1. Be sure to update the [GitLab CE/EE documentation](../administration/logs/index.md) and the [GitLab.com
- runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/docs/logging/README.md).
+1. Be sure to update the [GitLab CE/EE documentation](../administration/logs/index.md) and the
+ [GitLab.com runbooks](https://gitlab.com/gitlab-com/runbooks/blob/master/docs/logging/README.md).
## Control logging visibility
diff --git a/doc/user/usage_quotas.md b/doc/user/usage_quotas.md
index b85cab1cf47..6044049b260 100644
--- a/doc/user/usage_quotas.md
+++ b/doc/user/usage_quotas.md
@@ -39,16 +39,19 @@ To prevent exceeding the namespace storage quota, you can:
Starting October 19, 2022, a storage limit will be enforced on all GitLab Free namespaces.
We will start with a large limit enforcement and eventually reduce it to 5 GB.
+Impacted users are notified via email and in-app notifications will begin 2022-08-22.
+Only GitLab SaaS users are impacted - the limits are not applicable to self-managed users.
+
The following table describes the enforcement schedule, which is subject to change.
-| Target enforcement date | Limit | Expected Impact | Status |
-| ------ | ------ | ------ | ------ |
-| October 19, 2022 | 45,000 GB | LOW | Pending (**{hourglass}**)|
-| October 20, 2022 | 7,500 GB | LOW | Pending (**{hourglass}**)|
-| October 24, 2022 | 500 GB | MEDIUM | Pending (**{hourglass}**)|
-| October 27, 2022 | 75 GB | MEDIUM HIGH | Pending (**{hourglass}**)|
-| November 2, 2022 | 10 GB | HIGH | Pending (**{hourglass}**)|
-| November 9, 2022 | 5 GB | VERY HIGH | Pending (**{hourglass}**)|
+| Target enforcement date | Limit | Status |
+| ----------------------- | ----- | ------ |
+| October 19, 2022 | 45,000 GB | Pending (**{hourglass}**)|
+| October 20, 2022 | 7,500 GB | Pending (**{hourglass}**)|
+| October 24, 2022 | 500 GB | Pending (**{hourglass}**)|
+| October 27, 2022 | 75 GB | Pending (**{hourglass}**)|
+| November 2, 2022 | 10 GB | Pending (**{hourglass}**)|
+| November 9, 2022 | 5 GB | Pending (**{hourglass}**)|
Namespaces that reach the enforced limit will have their projects locked. To unlock your project, you will have to [manage its storage](#manage-your-storage-usage).
diff --git a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
index 074f2f0f78e..a4baca12f59 100644
--- a/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/DAST-Default-Branch-Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.30.0'
+ DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.33.0'
TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
.dast-auto-deploy:
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 2478b14348a..d4e1539ae39 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_DEPLOY_IMAGE_VERSION: 'v2.30.0'
+ AUTO_DEPLOY_IMAGE_VERSION: 'v2.33.0'
TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
.auto-deploy:
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
index 37ba6c5cb47..591ef49d146 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.latest.gitlab-ci.yml
@@ -1,5 +1,5 @@
variables:
- AUTO_DEPLOY_IMAGE_VERSION: 'v2.30.0'
+ AUTO_DEPLOY_IMAGE_VERSION: 'v2.33.0'
TEMPLATE_REGISTRY_HOST: 'registry.gitlab.com'
.auto-deploy:
diff --git a/lib/gitlab/github_import/importer/events/base_importer.rb b/lib/gitlab/github_import/importer/events/base_importer.rb
index d9e16a7c91e..9ab1d916d33 100644
--- a/lib/gitlab/github_import/importer/events/base_importer.rb
+++ b/lib/gitlab/github_import/importer/events/base_importer.rb
@@ -7,10 +7,10 @@ module Gitlab
# Base class for importing issue events during project import from GitHub
class BaseImporter
# project - An instance of `Project`.
- # user_finder - An instance of `Gitlab::GithubImport::UserFinder`.
- def initialize(project, user_finder)
+ # client - An instance of `Gitlab::GithubImport::Client`.
+ def initialize(project, client)
@project = project
- @user_finder = user_finder
+ @user_finder = UserFinder.new(project, client)
end
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
@@ -25,6 +25,10 @@ module Gitlab
def author_id(issue_event, author_key: :actor)
user_finder.author_id_for(issue_event, author_key: author_key).first
end
+
+ def issuable_db_id(object)
+ IssuableFinder.new(project, object).database_id
+ end
end
end
end
diff --git a/lib/gitlab/github_import/importer/events/changed_assignee.rb b/lib/gitlab/github_import/importer/events/changed_assignee.rb
index 6930ea0ef8d..c8f6335e4a8 100644
--- a/lib/gitlab/github_import/importer/events/changed_assignee.rb
+++ b/lib/gitlab/github_import/importer/events/changed_assignee.rb
@@ -20,7 +20,7 @@ module Gitlab
Note.create!(
system: true,
noteable_type: Issue.name,
- noteable_id: issue_event.issue_db_id,
+ noteable_id: issuable_db_id(issue_event),
project: project,
author_id: assigner_id,
note: note_body,
diff --git a/lib/gitlab/github_import/importer/events/changed_label.rb b/lib/gitlab/github_import/importer/events/changed_label.rb
index c3350251b2d..818a9202745 100644
--- a/lib/gitlab/github_import/importer/events/changed_label.rb
+++ b/lib/gitlab/github_import/importer/events/changed_label.rb
@@ -13,7 +13,7 @@ module Gitlab
def create_event(issue_event)
ResourceLabelEvent.create!(
- issue_id: issue_event.issue_db_id,
+ issue_id: issuable_db_id(issue_event),
user_id: author_id(issue_event),
label_id: label_finder.id_for(issue_event.label_title),
action: action(issue_event.event),
diff --git a/lib/gitlab/github_import/importer/events/changed_milestone.rb b/lib/gitlab/github_import/importer/events/changed_milestone.rb
index a3e4d2efb73..3164c041dc3 100644
--- a/lib/gitlab/github_import/importer/events/changed_milestone.rb
+++ b/lib/gitlab/github_import/importer/events/changed_milestone.rb
@@ -18,7 +18,7 @@ module Gitlab
def create_event(issue_event)
ResourceMilestoneEvent.create!(
- issue_id: issue_event.issue_db_id,
+ issue_id: issuable_db_id(issue_event),
user_id: author_id(issue_event),
created_at: issue_event.created_at,
milestone_id: project.milestones.find_by_title(issue_event.milestone_title)&.id,
diff --git a/lib/gitlab/github_import/importer/events/closed.rb b/lib/gitlab/github_import/importer/events/closed.rb
index 1db326328f6..ca8730d0f27 100644
--- a/lib/gitlab/github_import/importer/events/closed.rb
+++ b/lib/gitlab/github_import/importer/events/closed.rb
@@ -18,7 +18,7 @@ module Gitlab
author_id: author_id(issue_event),
action: 'closed',
target_type: Issue.name,
- target_id: issue_event.issue_db_id,
+ target_id: issuable_db_id(issue_event),
created_at: issue_event.created_at,
updated_at: issue_event.created_at
)
@@ -27,7 +27,7 @@ module Gitlab
def create_state_event(issue_event)
ResourceStateEvent.create!(
user_id: author_id(issue_event),
- issue_id: issue_event.issue_db_id,
+ issue_id: issuable_db_id(issue_event),
source_commit: issue_event.commit_id,
state: 'closed',
close_after_error_tracking_resolve: false,
diff --git a/lib/gitlab/github_import/importer/events/cross_referenced.rb b/lib/gitlab/github_import/importer/events/cross_referenced.rb
index 9a3a2536b7c..89fc1bdeb09 100644
--- a/lib/gitlab/github_import/importer/events/cross_referenced.rb
+++ b/lib/gitlab/github_import/importer/events/cross_referenced.rb
@@ -34,7 +34,7 @@ module Gitlab
Note.create!(
system: true,
noteable_type: Issue.name,
- noteable_id: issue_event.issue_db_id,
+ noteable_id: issuable_db_id(issue_event),
project: project,
author_id: user_id,
note: note_body,
@@ -66,7 +66,7 @@ module Gitlab
iid: number, issuable_type: record_class.name
)
- Gitlab::GithubImport::IssuableFinder.new(project, mentioned_in_adapter).database_id
+ issuable_db_id(mentioned_in_adapter)
end
def cross_reference_note_content(gfm_reference)
diff --git a/lib/gitlab/github_import/importer/events/renamed.rb b/lib/gitlab/github_import/importer/events/renamed.rb
index 44f05eb4af8..96d112b04c6 100644
--- a/lib/gitlab/github_import/importer/events/renamed.rb
+++ b/lib/gitlab/github_import/importer/events/renamed.rb
@@ -13,7 +13,7 @@ module Gitlab
def note_params(issue_event)
{
- noteable_id: issue_event.issue_db_id,
+ noteable_id: issuable_db_id(issue_event),
noteable_type: Issue.name,
project_id: project.id,
author_id: author_id(issue_event),
diff --git a/lib/gitlab/github_import/importer/events/reopened.rb b/lib/gitlab/github_import/importer/events/reopened.rb
index 923fee51394..b75344bf817 100644
--- a/lib/gitlab/github_import/importer/events/reopened.rb
+++ b/lib/gitlab/github_import/importer/events/reopened.rb
@@ -18,7 +18,7 @@ module Gitlab
author_id: author_id(issue_event),
action: 'reopened',
target_type: Issue.name,
- target_id: issue_event.issue_db_id,
+ target_id: issuable_db_id(issue_event),
created_at: issue_event.created_at,
updated_at: issue_event.created_at
)
@@ -27,7 +27,7 @@ module Gitlab
def create_state_event(issue_event)
ResourceStateEvent.create!(
user_id: author_id(issue_event),
- issue_id: issue_event.issue_db_id,
+ issue_id: issuable_db_id(issue_event),
state: 'reopened',
created_at: issue_event.created_at
)
diff --git a/lib/gitlab/github_import/importer/issue_event_importer.rb b/lib/gitlab/github_import/importer/issue_event_importer.rb
index fb52c18853c..ef456e56ee1 100644
--- a/lib/gitlab/github_import/importer/issue_event_importer.rb
+++ b/lib/gitlab/github_import/importer/issue_event_importer.rb
@@ -4,7 +4,7 @@ module Gitlab
module GithubImport
module Importer
class IssueEventImporter
- attr_reader :issue_event, :project, :client, :user_finder
+ attr_reader :issue_event, :project, :client
# issue_event - An instance of `Gitlab::GithubImport::Representation::IssueEvent`.
# project - An instance of `Project`.
@@ -13,29 +13,16 @@ module Gitlab
@issue_event = issue_event
@project = project
@client = client
- @user_finder = UserFinder.new(project, client)
end
+ # TODO: Add MergeRequest events support
+ # https://gitlab.com/groups/gitlab-org/-/epics/7673
def execute
- event_importer = case issue_event.event
- when 'closed'
- Gitlab::GithubImport::Importer::Events::Closed
- when 'reopened'
- Gitlab::GithubImport::Importer::Events::Reopened
- when 'labeled', 'unlabeled'
- Gitlab::GithubImport::Importer::Events::ChangedLabel
- when 'renamed'
- Gitlab::GithubImport::Importer::Events::Renamed
- when 'milestoned', 'demilestoned'
- Gitlab::GithubImport::Importer::Events::ChangedMilestone
- when 'cross-referenced'
- Gitlab::GithubImport::Importer::Events::CrossReferenced
- when 'assigned', 'unassigned'
- Gitlab::GithubImport::Importer::Events::ChangedAssignee
- end
+ return if issue_event.issuable_type == 'MergeRequest'
- if event_importer
- event_importer.new(project, user_finder).execute(issue_event)
+ importer = event_importer_class(issue_event)
+ if importer
+ importer.new(project, client).execute(issue_event)
else
Gitlab::GithubImport::Logger.debug(
message: 'UNSUPPORTED_EVENT_TYPE',
@@ -43,6 +30,27 @@ module Gitlab
)
end
end
+
+ private
+
+ def event_importer_class(issue_event)
+ case issue_event.event
+ when 'closed'
+ Gitlab::GithubImport::Importer::Events::Closed
+ when 'reopened'
+ Gitlab::GithubImport::Importer::Events::Reopened
+ when 'labeled', 'unlabeled'
+ Gitlab::GithubImport::Importer::Events::ChangedLabel
+ when 'renamed'
+ Gitlab::GithubImport::Importer::Events::Renamed
+ when 'milestoned', 'demilestoned'
+ Gitlab::GithubImport::Importer::Events::ChangedMilestone
+ when 'cross-referenced'
+ Gitlab::GithubImport::Importer::Events::CrossReferenced
+ when 'assigned', 'unassigned'
+ Gitlab::GithubImport::Importer::Events::ChangedAssignee
+ end
+ end
end
end
end
diff --git a/lib/gitlab/github_import/importer/issue_events_importer.rb b/lib/gitlab/github_import/importer/issue_events_importer.rb
new file mode 100644
index 00000000000..71dd99f91f9
--- /dev/null
+++ b/lib/gitlab/github_import/importer/issue_events_importer.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GithubImport
+ module Importer
+ class IssueEventsImporter
+ include ParallelScheduling
+
+ def importer_class
+ IssueEventImporter
+ end
+
+ def representation_class
+ Representation::IssueEvent
+ end
+
+ def sidekiq_worker_class
+ ImportIssueEventWorker
+ end
+
+ def object_type
+ :issue_event
+ end
+
+ def collection_method
+ :repository_issue_events
+ end
+
+ def id_for_already_imported_cache(event)
+ event.id
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb b/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb
index 45bbc25e637..8e4015acbbc 100644
--- a/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb
+++ b/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer.rb
@@ -18,13 +18,16 @@ module Gitlab
{ project: project.id, collection: collection_method }
end
+ # In single endpoint there is no issue info to which associated related
+ # To make it possible to identify issue in separated worker we need to patch
+ # Sawyer instances here with issue number
def each_associated(parent_record, associated)
compose_associated_id!(parent_record, associated)
return if already_imported?(associated)
Gitlab::GithubImport::ObjectCounter.increment(project, object_type, :fetched)
- associated.issue_db_id = parent_record.id
+ associated.issue = { 'number' => parent_record.iid }
yield(associated)
mark_as_imported(associated)
diff --git a/lib/gitlab/github_import/issuable_finder.rb b/lib/gitlab/github_import/issuable_finder.rb
index da205ebd345..e7a1b7b3368 100644
--- a/lib/gitlab/github_import/issuable_finder.rb
+++ b/lib/gitlab/github_import/issuable_finder.rb
@@ -69,6 +69,8 @@ module Gitlab
object.noteable_id
elsif object.respond_to?(:iid)
object.iid
+ elsif object.respond_to?(:issuable_id)
+ object.issuable_id
else
raise(
TypeError,
diff --git a/lib/gitlab/github_import/representation/issue_event.rb b/lib/gitlab/github_import/representation/issue_event.rb
index 3561628bdde..b01887f50ee 100644
--- a/lib/gitlab/github_import/representation/issue_event.rb
+++ b/lib/gitlab/github_import/representation/issue_event.rb
@@ -10,8 +10,7 @@ module Gitlab
attr_reader :attributes
expose_attribute :id, :actor, :event, :commit_id, :label_title, :old_title, :new_title,
- :milestone_title, :source, :assignee, :assigner, :created_at
- expose_attribute :issue_db_id # set in SingleEndpointIssueEventsImporter#each_associated
+ :milestone_title, :issue, :source, :assignee, :assigner, :created_at
# attributes - A Hash containing the event details. The keys of this
# Hash (and any nested hashes) must be symbols.
@@ -23,6 +22,14 @@ module Gitlab
{ id: id }
end
+ def issuable_type
+ issue && issue[:pull_request].present? ? 'MergeRequest' : 'Issue'
+ end
+
+ def issuable_id
+ issue && issue[:number]
+ end
+
class << self
# Builds an event from a GitHub API response.
#
@@ -37,10 +44,10 @@ module Gitlab
old_title: event.rename && event.rename[:from],
new_title: event.rename && event.rename[:to],
milestone_title: event.milestone && event.milestone[:title],
+ issue: event.issue&.to_h&.symbolize_keys,
source: event.source,
assignee: user_representation(event.assignee),
assigner: user_representation(event.assigner),
- issue_db_id: event.issue_db_id,
created_at: event.created_at
)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ca4949b0166..d5f1ec3bd2f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5629,7 +5629,7 @@ msgstr ""
msgid "Automatically resolved"
msgstr ""
-msgid "Automatically update this project's branches and tags from the upstream repository every hour."
+msgid "Automatically update this project's branches and tags from the upstream repository."
msgstr ""
msgid "Autosave|Note"
@@ -19493,6 +19493,9 @@ msgstr ""
msgid "How do I use file templates?"
msgstr ""
+msgid "How does pull mirroring work?"
+msgstr ""
+
msgid "How many seconds an IP counts toward the IP address limit."
msgstr ""
@@ -34913,6 +34916,9 @@ msgstr ""
msgid "SecurityOrchestration|Failed to load cluster agents."
msgstr ""
+msgid "SecurityOrchestration|Failed to load vulnerability scanners."
+msgstr ""
+
msgid "SecurityOrchestration|If any scanner finds a newly detected critical vulnerability in an open merge request targeting the master branch, then require two approvals from any member of App security."
msgstr ""
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index 501c43d6bd0..53104a51398 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -13,7 +13,8 @@ module QA
:github_personal_access_token,
:github_repository_path,
:gitlab_repository_path,
- :personal_namespace
+ :personal_namespace,
+ :description
attr_reader :repository_storage
@@ -21,7 +22,6 @@ module QA
:name,
:path,
:add_name_uuid,
- :description,
:runners_token,
:visibility,
:template_name,
@@ -108,7 +108,7 @@ module QA
end
new_page.choose_name(@name)
- new_page.add_description(@description)
+ new_page.add_description(@description) if @description
new_page.set_visibility(@visibility)
new_page.disable_initialize_with_sast
new_page.disable_initialize_with_readme unless @initialize_with_readme
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 0063ce2613a..2d4ec6d5f72 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -10,7 +10,6 @@ module QA
expect(project_page).to have_content(
/Project \S?#{project_name}\S+ was successfully created/
)
- expect(project_page).to have_content('create awesome project test')
expect(project_page).to have_content('The repository for this project is empty')
end
end
@@ -26,7 +25,7 @@ module QA
let(:project) do
Resource::Project.fabricate_via_browser_ui! do |project|
project.name = project_name
- project.description = 'create awesome project test'
+ project.description = nil
end
end
@@ -38,8 +37,8 @@ module QA
let(:project) do
Resource::Project.fabricate_via_browser_ui! do |project|
project.name = project_name
- project.description = 'create awesome project test'
project.personal_namespace = Runtime::User.username
+ project.description = nil
end
end
diff --git a/qa/qa/support/parallel_pipeline_jobs.rb b/qa/qa/support/parallel_pipeline_jobs.rb
new file mode 100644
index 00000000000..c37958267de
--- /dev/null
+++ b/qa/qa/support/parallel_pipeline_jobs.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+module QA
+ module Support
+ # Helper utility to fetch parallel job names in a given pipelines stage
+ #
+ class ParallelPipelineJobs
+ include Support::API
+
+ PARALLEL_JOB_NAME_PATTERN = %r{^\S+ \d+/\d+$}.freeze
+
+ def initialize(stage_name:, project_id:, pipeline_id:, access_token:)
+ @stage_name = stage_name
+ @access_token = access_token
+ @project_id = project_id || raise("project_id must be provided")
+ @pipeline_id = pipeline_id || raise("pipeline_id must be provided")
+ end
+
+ # Fetch parallel job names in given stage
+ #
+ # Default to arguments available on CI
+ #
+ # @param [String] stage_name
+ # @param [Integer] project_id
+ # @param [Integer] pipeline_id
+ # @param [String] access_token
+ # @return [Array]
+ def self.fetch(
+ stage_name:,
+ access_token:,
+ project_id: ENV["CI_PROJECT_ID"],
+ pipeline_id: ENV["CI_PIPELINE_ID"]
+ )
+ new(
+ stage_name: stage_name,
+ project_id: project_id,
+ pipeline_id: pipeline_id,
+ access_token: access_token
+ ).parallel_jobs
+ end
+
+ # Parallel job list
+ #
+ # @return [Array<String>]
+ def parallel_jobs
+ api_get("projects/#{project_id}/pipelines/#{pipeline_id}/jobs?per_page=100")
+ .select { |job| job[:stage] == stage_name && job[:name].match?(PARALLEL_JOB_NAME_PATTERN) }
+ .map { |job| job[:name].gsub(%r{ \d+/\d+}, "") }
+ .uniq
+ end
+
+ private
+
+ attr_reader :stage_name, :access_token, :project_id, :pipeline_id
+
+ # Api get request
+ #
+ # @param [String] path
+ # @param [Hash] payload
+ # @return [Hash, Array]
+ def api_get(path)
+ response = get("#{api_url}/#{path}", { headers: { "PRIVATE-TOKEN" => access_token } })
+ parse_body(response)
+ end
+
+ # Gitlab api url
+ #
+ # @return [String]
+ def api_url
+ @api_url ||= ENV['CI_API_V4_URL'] || "https://gitlab.com/api/v4"
+ end
+ end
+ end
+end
diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake
index fe9a9c4586f..c1225964aef 100644
--- a/qa/tasks/knapsack.rake
+++ b/qa/tasks/knapsack.rake
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# rubocop:disable Rails/RakeEnvironment
namespace :knapsack do
desc "Run tests with knapsack runner"
task :rspec, [:rspec_args] do |_, args|
@@ -16,11 +15,26 @@ namespace :knapsack do
exit QA::Specs::KnapsackRunner.run(rspec_args)
end
- desc "Download latest knapsack report or multiple reports passed via QA_KNAPSACK_REPORTS env variable"
- task :download do
- next QA::Support::KnapsackReport.download_report unless ENV["QA_KNAPSACK_REPORTS"]
+ desc "Download latest knapsack reports for parallel jobs"
+ task :download, [:stage_name] do |_, args|
+ test_stage_name = args[:stage_name]
- ENV["QA_KNAPSACK_REPORTS"].split(",").each do |report_name|
+ # QA_KNAPSACK_REPORTS remains for changes to be backwards compatible
+ # TODO: remove and only use automated detection once changes are merged
+ unless ENV["QA_KNAPSACK_REPORTS"] || test_stage_name
+ QA::Runtime::Logger.warn("Missing QA_KNAPSACK_REPORTS environment variable or test stage name for autodetection")
+ next
+ end
+
+ reports = if test_stage_name
+ QA::Support::ParallelPipelineJobs
+ .fetch(stage_name: test_stage_name, access_token: ENV["QA_GITLAB_CI_TOKEN"])
+ .map { |job| job.tr(":", "-") }
+ else
+ ENV["QA_KNAPSACK_REPORTS"].split(",")
+ end
+
+ reports.each do |report_name|
QA::Support::KnapsackReport.new(report_name).download_report
rescue StandardError => e
QA::Runtime::Logger.error(e)
@@ -37,4 +51,3 @@ namespace :knapsack do
QA::Tools::LongRunningSpecReporter.execute
end
end
-# rubocop:enable Rails/RakeEnvironment
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index e03a8b7d321..14b198dbefe 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -12,47 +12,6 @@ RSpec.describe SearchController do
sign_in(user)
end
- shared_examples_for 'when the user cannot read cross project' do |action, params|
- before do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?)
- .with(user, :read_cross_project, :global) { false }
- end
-
- it 'blocks access without a project_id' do
- get action, params: params
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'allows access with a project_id' do
- get action, params: params.merge(project_id: create(:project, :public).id)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- shared_examples_for 'with external authorization service enabled' do |action, params|
- let(:project) { create(:project, namespace: user.namespace) }
- let(:note) { create(:note_on_issue, project: project) }
-
- before do
- enable_external_authorization_service_check
- end
-
- it 'renders a 403 when no project is given' do
- get action, params: params
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
-
- it 'renders a 200 when a project was set' do
- get action, params: params.merge(project_id: project.id)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
shared_examples_for 'support for active record query timeouts' do |action, params, method_to_stub, format|
before do
allow_next_instance_of(SearchService) do |service|
@@ -133,10 +92,11 @@ RSpec.describe SearchController do
{
chars_under_limit: (('a' * (term_char_limit - 1) + ' ') * (term_limit - 1))[0, char_limit],
chars_over_limit: (('a' * (term_char_limit - 1) + ' ') * (term_limit - 1))[0, char_limit + 1],
- terms_under_limit: ('abc ' * (term_limit - 1)),
+ terms_under_limit: ('abc ' * (term_limit - 1)),
terms_over_limit: ('abc ' * (term_limit + 1)),
term_length_over_limit: ('a' * (term_char_limit + 1)),
- term_length_under_limit: ('a' * (term_char_limit - 1))
+ term_length_under_limit: ('a' * (term_char_limit - 1)),
+ blank: ''
}
end
@@ -147,6 +107,7 @@ RSpec.describe SearchController do
:terms_over_limit | :set_terms_flash
:term_length_under_limit | :not_to_set_flash
:term_length_over_limit | :not_to_set_flash # abuse, so do nothing.
+ :blank | :not_to_set_flash
end
with_them do
@@ -417,7 +378,7 @@ RSpec.describe SearchController do
expect(payload[:metadata]['meta.search.project_ids']).to eq(%w(456 789))
expect(payload[:metadata]['meta.search.type']).to eq('basic')
expect(payload[:metadata]['meta.search.level']).to eq('global')
- expect(payload[:metadata]['meta.search.language']).to eq('ruby')
+ expect(payload[:metadata]['meta.search.filters.language']).to eq('ruby')
end
get :show, params: {
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 9d2d1454d77..f45025d079a 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -3,16 +3,44 @@
require 'spec_helper'
RSpec.describe 'New project', :js do
- include Select2Helper
include Spec::Support::Helpers::Features::TopNavSpecHelpers
context 'as a user' do
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
+ it 'shows the project description field when it should' do
+ description_label = 'Project description (optional)'
+
+ visit new_project_path
+ click_link 'Create blank project'
+
+ page.within('#blank-project-pane') do
+ expect(page).not_to have_content(description_label)
+ end
+
+ visit new_project_path
+ click_link 'Import project'
+
+ page.within('#import-project-pane') do
+ click_button 'Repository by URL'
+
+ expect(page).to have_content(description_label)
+ end
+
+ visit new_project_path
+ click_link 'Create from template'
+
+ page.within('#create-from-template-pane') do
+ find("[data-testid='use_template_#{Gitlab::ProjectTemplate.localized_templates_table.first.name}']").click
+
+ expect(page).to have_content(description_label)
+ end
+ end
+
it 'shows a message if multiple levels are restricted' do
Gitlab::CurrentSettings.update!(
restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL]
diff --git a/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
new file mode 100644
index 00000000000..e9966576cab
--- /dev/null
+++ b/spec/frontend/ci_variable_list/components/ci_environments_dropdown_spec.js
@@ -0,0 +1,139 @@
+import { GlDropdown, GlDropdownItem, GlIcon, GlSearchBoxByType } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import { nextTick } from 'vue';
+import { allEnvironments } from '~/ci_variable_list/constants';
+import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
+
+describe('Ci environments dropdown', () => {
+ let wrapper;
+
+ const envs = ['dev', 'prod', 'staging'];
+ const defaultProps = { environments: envs, selectedEnvironmentScope: '' };
+
+ const findDropdownText = () => wrapper.findComponent(GlDropdown).text();
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index);
+ const findActiveIconByIndex = (index) => findDropdownItemByIndex(index).findComponent(GlIcon);
+ const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+
+ const createComponent = ({ props = {}, searchTerm = '' } = {}) => {
+ wrapper = mount(CiEnvironmentsDropdown, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+
+ findSearchBox().vm.$emit('input', searchTerm);
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('No environments found', () => {
+ beforeEach(() => {
+ createComponent({ searchTerm: 'stable' });
+ });
+
+ it('renders create button with search term if environments do not contain search term', () => {
+ expect(findAllDropdownItems()).toHaveLength(2);
+ expect(findDropdownItemByIndex(1).text()).toBe('Create wildcard: stable');
+ });
+
+ it('renders empty results message', () => {
+ expect(findDropdownItemByIndex(0).text()).toBe('No matching results');
+ });
+ });
+
+ describe('Search term is empty', () => {
+ beforeEach(() => {
+ createComponent({ props: { environments: envs } });
+ });
+
+ it('renders all environments when search term is empty', () => {
+ expect(findAllDropdownItems()).toHaveLength(3);
+ expect(findDropdownItemByIndex(0).text()).toBe(envs[0]);
+ expect(findDropdownItemByIndex(1).text()).toBe(envs[1]);
+ expect(findDropdownItemByIndex(2).text()).toBe(envs[2]);
+ });
+
+ it('should not display active checkmark on the inactive stage', () => {
+ expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(true);
+ });
+ });
+
+ describe('when `*` is the value of selectedEnvironmentScope props', () => {
+ const wildcardScope = '*';
+
+ beforeEach(() => {
+ createComponent({ props: { selectedEnvironmentScope: wildcardScope } });
+ });
+
+ it('shows the `All environments` text and not the wildcard', () => {
+ expect(findDropdownText()).toContain(allEnvironments.text);
+ expect(findDropdownText()).not.toContain(wildcardScope);
+ });
+ });
+
+ describe('Environments found', () => {
+ const currentEnv = envs[2];
+
+ beforeEach(async () => {
+ createComponent({ searchTerm: currentEnv });
+ await nextTick();
+ });
+
+ it('renders only the environment searched for', () => {
+ expect(findAllDropdownItems()).toHaveLength(1);
+ expect(findDropdownItemByIndex(0).text()).toBe(currentEnv);
+ });
+
+ it('should not display create button', () => {
+ const environments = findAllDropdownItems().filter((env) => env.text().startsWith('Create'));
+ expect(environments).toHaveLength(0);
+ expect(findAllDropdownItems()).toHaveLength(1);
+ });
+
+ it('should not display empty results message', () => {
+ expect(wrapper.findComponent({ ref: 'noMatchingResults' }).exists()).toBe(false);
+ });
+
+ it('should clear the search term when showing the dropdown', () => {
+ wrapper.findComponent(GlDropdown).trigger('click');
+
+ expect(findSearchBox().text()).toBe('');
+ });
+
+ describe('Custom events', () => {
+ describe('when clicking on an environment', () => {
+ const itemIndex = 0;
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should emit `select-environment` if an environment is clicked', async () => {
+ await nextTick();
+
+ await findDropdownItemByIndex(itemIndex).vm.$emit('click');
+
+ expect(wrapper.emitted('select-environment')).toEqual([[envs[itemIndex]]]);
+ });
+ });
+
+ describe('when creating a new environment from a search term', () => {
+ const search = 'new-env';
+ beforeEach(() => {
+ createComponent({ searchTerm: search });
+ });
+
+ it('should emit createClicked if an environment is clicked', async () => {
+ await nextTick();
+ findDropdownItemByIndex(1).vm.$emit('click');
+ expect(wrapper.emitted('create-environment-scope')).toEqual([[search]]);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
new file mode 100644
index 00000000000..51b902d97dc
--- /dev/null
+++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js
@@ -0,0 +1,380 @@
+import { GlButton, GlFormInput } from '@gitlab/ui';
+import { mockTracking } from 'helpers/tracking_helper';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
+import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
+import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
+import {
+ ADD_VARIABLE_ACTION,
+ AWS_ACCESS_KEY_ID,
+ EDIT_VARIABLE_ACTION,
+ EVENT_LABEL,
+ EVENT_ACTION,
+ ENVIRONMENT_SCOPE_LINK_TITLE,
+} from '~/ci_variable_list/constants';
+import { mockVariablesWithScopes } from '../mocks';
+import ModalStub from '../stubs';
+
+describe('Ci variable modal', () => {
+ let wrapper;
+ let trackingSpy;
+
+ const maskableRegex = '^[a-zA-Z0-9_+=/@:.~-]{8,}$';
+
+ const defaultProvide = {
+ awsLogoSvgPath: '/logo',
+ awsTipCommandsLink: '/tips',
+ awsTipDeployLink: '/deploy',
+ awsTipLearnLink: '/learn-link',
+ containsVariableReferenceLink: '/reference',
+ environmentScopeLink: '/help/environments',
+ isProtectedByDefault: false,
+ maskedEnvironmentVariablesLink: '/variables-link',
+ maskableRegex,
+ protectedEnvironmentVariablesLink: '/protected-link',
+ };
+
+ const defaultProps = {
+ areScopedVariablesAvailable: true,
+ environments: [],
+ mode: ADD_VARIABLE_ACTION,
+ selectedVariable: {},
+ };
+
+ const createComponent = ({ mountFn = shallowMountExtended, props = {}, provide = {} } = {}) => {
+ wrapper = mountFn(CiVariableModal, {
+ attachTo: document.body,
+ provide: { ...defaultProvide, ...provide },
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ stubs: {
+ GlModal: ModalStub,
+ },
+ });
+ };
+
+ const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
+ const findReferenceWarning = () => wrapper.findByTestId('contains-variable-reference');
+ const findModal = () => wrapper.find(ModalStub);
+ const findAWSTip = () => wrapper.findByTestId('aws-guidance-tip');
+ const findAddorUpdateButton = () => wrapper.findByTestId('ciUpdateOrAddVariableBtn');
+ const deleteVariableButton = () =>
+ findModal()
+ .findAll(GlButton)
+ .wrappers.find((button) => button.props('variant') === 'danger');
+ const findProtectedVariableCheckbox = () =>
+ wrapper.findByTestId('ci-variable-protected-checkbox');
+ const findMaskedVariableCheckbox = () => wrapper.findByTestId('ci-variable-masked-checkbox');
+ const findValueField = () => wrapper.find('#ci-variable-value');
+ const findEnvScopeLink = () => wrapper.findByTestId('environment-scope-link');
+ const findEnvScopeInput = () => wrapper.findByTestId('environment-scope').find(GlFormInput);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('Adding a variable', () => {
+ describe('when no key/value pair are present', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('shows the submit button as disabled ', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeTruthy();
+ });
+ });
+
+ describe('when a key/value pair is present', () => {
+ beforeEach(() => {
+ createComponent({ props: { selectedVariable: mockVariablesWithScopes[0] } });
+ });
+
+ it('shows the submit button as enabled ', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeFalsy();
+ });
+ });
+
+ describe('events', () => {
+ const [currentVariable] = mockVariablesWithScopes;
+
+ beforeEach(() => {
+ createComponent({ props: { selectedVariable: currentVariable } });
+ jest.spyOn(wrapper.vm, '$emit');
+ });
+
+ it('Dispatches `add-variable` action on submit', () => {
+ findAddorUpdateButton().vm.$emit('click');
+ expect(wrapper.emitted('add-variable')).toEqual([[currentVariable]]);
+ });
+
+ it('Dispatches the `hideModal` event when dismissing', () => {
+ findModal().vm.$emit('hidden');
+ expect(wrapper.emitted('hideModal')).toEqual([[]]);
+ });
+ });
+ });
+
+ describe('when protected by default', () => {
+ describe('when adding a new variable', () => {
+ beforeEach(() => {
+ createComponent({ provide: { isProtectedByDefault: true } });
+ findModal().vm.$emit('shown');
+ });
+
+ it('updates the protected value to true', () => {
+ expect(
+ findProtectedVariableCheckbox().attributes('data-is-protected-checked'),
+ ).toBeTruthy();
+ });
+ });
+
+ describe('when editing a variable', () => {
+ beforeEach(() => {
+ createComponent({
+ provide: { isProtectedByDefault: false },
+ props: {
+ selectedVariable: {},
+ mode: EDIT_VARIABLE_ACTION,
+ },
+ });
+ findModal().vm.$emit('shown');
+ });
+
+ it('keeps the value as false', async () => {
+ expect(
+ findProtectedVariableCheckbox().attributes('data-is-protected-checked'),
+ ).toBeUndefined();
+ });
+ });
+ });
+
+ describe('Adding a new non-AWS variable', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ createComponent({ mountFn: mountExtended, props: { selectedVariable: variable } });
+ });
+
+ it('does not show AWS guidance tip', () => {
+ const tip = findAWSTip();
+ expect(tip.exists()).toBe(true);
+ expect(tip.isVisible()).toBe(false);
+ });
+ });
+
+ describe('Adding a new AWS variable', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ const AWSKeyVariable = {
+ ...variable,
+ key: AWS_ACCESS_KEY_ID,
+ value: 'AKIAIOSFODNN7EXAMPLEjdhy',
+ };
+ createComponent({ mountFn: mountExtended, props: { selectedVariable: AWSKeyVariable } });
+ });
+
+ it('shows AWS guidance tip', () => {
+ const tip = findAWSTip();
+ expect(tip.exists()).toBe(true);
+ expect(tip.isVisible()).toBe(true);
+ });
+ });
+
+ describe('Reference warning when adding a variable', () => {
+ describe('with a $ character', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ const variableWithDollarSign = {
+ ...variable,
+ value: 'valueWith$',
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: variableWithDollarSign },
+ });
+ });
+
+ it(`renders the variable reference warning`, () => {
+ expect(findReferenceWarning().exists()).toBe(true);
+ });
+ });
+
+ describe('without a $ character', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: variable },
+ });
+ });
+
+ it(`does not render the variable reference warning`, () => {
+ expect(findReferenceWarning().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('Editing a variable', () => {
+ const [variable] = mockVariablesWithScopes;
+
+ beforeEach(() => {
+ createComponent({ props: { selectedVariable: variable, mode: EDIT_VARIABLE_ACTION } });
+ jest.spyOn(wrapper.vm, '$emit');
+ });
+
+ it('button text is Update variable when updating', () => {
+ expect(findAddorUpdateButton().text()).toBe('Update variable');
+ });
+
+ it('Update variable button dispatches updateVariable with correct variable', () => {
+ findAddorUpdateButton().vm.$emit('click');
+ expect(wrapper.emitted('update-variable')).toEqual([[variable]]);
+ });
+
+ it('Propagates the `hideModal` event', () => {
+ findModal().vm.$emit('hidden');
+ expect(wrapper.emitted('hideModal')).toEqual([[]]);
+ });
+
+ it('dispatches `delete-variable` with correct variable to delete', () => {
+ deleteVariableButton().vm.$emit('click');
+ expect(wrapper.emitted('delete-variable')).toEqual([[variable]]);
+ });
+ });
+
+ describe('Environment scope', () => {
+ describe('when feature is available', () => {
+ it('renders the environment dropdown', () => {
+ createComponent({
+ mountFn: mountExtended,
+ props: {
+ areScopedVariablesAvailable: true,
+ },
+ });
+
+ expect(findCiEnvironmentsDropdown().exists()).toBe(true);
+ expect(findCiEnvironmentsDropdown().isVisible()).toBe(true);
+ });
+
+ it('renders a link to documentation on scopes', () => {
+ createComponent({ mountFn: mountExtended });
+
+ const link = findEnvScopeLink();
+
+ expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE);
+ expect(link.attributes('href')).toBe(defaultProvide.environmentScopeLink);
+ });
+ });
+
+ describe('when feature is not available', () => {
+ it('disables the dropdown', () => {
+ createComponent({
+ mountFn: mountExtended,
+ props: {
+ areScopedVariablesAvailable: false,
+ },
+ });
+
+ expect(findCiEnvironmentsDropdown().exists()).toBe(false);
+ expect(findEnvScopeInput().attributes('readonly')).toBe('readonly');
+ });
+ });
+ });
+
+ describe('Validations', () => {
+ const maskError = 'This variable can not be masked.';
+
+ describe('when the mask state is invalid', () => {
+ beforeEach(async () => {
+ const [variable] = mockVariablesWithScopes;
+ const invalidMaskVariable = {
+ ...variable,
+ value: 'd:;',
+ masked: false,
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: invalidMaskVariable },
+ });
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ await findMaskedVariableCheckbox().trigger('click');
+ });
+
+ it('disables the submit button', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeTruthy();
+ });
+
+ it('shows the correct error text', () => {
+ expect(findModal().text()).toContain(maskError);
+ });
+
+ it('sends the correct tracking event', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, {
+ label: EVENT_LABEL,
+ property: ';',
+ });
+ });
+ });
+
+ describe.each`
+ value | masked | eventSent | trackingErrorProperty
+ ${'secretValue'} | ${false} | ${0} | ${null}
+ ${'short'} | ${true} | ${0} | ${null}
+ ${'dollar$ign'} | ${false} | ${1} | ${'$'}
+ ${'dollar$ign'} | ${true} | ${1} | ${'$'}
+ ${'unsupported|char'} | ${true} | ${1} | ${'|'}
+ ${'unsupported|char'} | ${false} | ${0} | ${null}
+ `('Adding a new variable', ({ value, masked, eventSent, trackingErrorProperty }) => {
+ beforeEach(async () => {
+ const [variable] = mockVariablesWithScopes;
+ const invalidKeyVariable = {
+ ...variable,
+ value: '',
+ masked: false,
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: invalidKeyVariable },
+ });
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ await findValueField().vm.$emit('input', value);
+ if (masked) {
+ await findMaskedVariableCheckbox().trigger('click');
+ }
+ });
+
+ it(`${
+ eventSent > 0 ? 'sends the correct' : 'does not send the'
+ } variable validation tracking event with ${value}`, () => {
+ expect(trackingSpy).toHaveBeenCalledTimes(eventSent);
+
+ if (eventSent > 0) {
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, {
+ label: EVENT_LABEL,
+ property: trackingErrorProperty,
+ });
+ }
+ });
+ });
+
+ describe('when masked variable has acceptable value', () => {
+ beforeEach(() => {
+ const [variable] = mockVariablesWithScopes;
+ const validMaskandKeyVariable = {
+ ...variable,
+ key: AWS_ACCESS_KEY_ID,
+ value: '12345678',
+ masked: true,
+ };
+ createComponent({
+ mountFn: mountExtended,
+ props: { selectedVariable: validMaskandKeyVariable },
+ });
+ });
+
+ it('does not disable the submit button', () => {
+ expect(findAddorUpdateButton().attributes('disabled')).toBeFalsy();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci_variable_list/mocks.js b/spec/frontend/ci_variable_list/mocks.js
new file mode 100644
index 00000000000..1ba50c74152
--- /dev/null
+++ b/spec/frontend/ci_variable_list/mocks.js
@@ -0,0 +1,116 @@
+import { variableTypes } from '~/ci_variable_list/constants';
+
+export const devName = 'dev';
+export const prodName = 'prod';
+
+export const mockVariables = [
+ {
+ __typename: 'CiVariable',
+ id: 1,
+ key: 'my-var',
+ masked: false,
+ protected: true,
+ value: 'env_val',
+ variableType: variableTypes.variableType,
+ },
+ {
+ __typename: 'CiVariable',
+ id: 2,
+ key: 'secret',
+ masked: true,
+ protected: false,
+ value: 'the_secret_value',
+ variableType: variableTypes.fileType,
+ },
+];
+
+export const mockVariablesWithScopes = mockVariables.map((variable) => {
+ return { ...variable, environmentScope: '*' };
+});
+
+const createDefaultVars = ({ withScope = true } = {}) => {
+ let base = mockVariables;
+
+ if (withScope) {
+ base = mockVariablesWithScopes;
+ }
+
+ return {
+ __typename: 'CiVariableConnection',
+ nodes: base,
+ };
+};
+
+const defaultEnvs = {
+ __typename: 'EnvironmentConnection',
+ nodes: [
+ {
+ __typename: 'Environment',
+ id: 1,
+ name: prodName,
+ },
+ {
+ __typename: 'Environment',
+ id: 2,
+ name: devName,
+ },
+ ],
+};
+
+export const mockEnvs = defaultEnvs.nodes;
+
+export const mockProjectEnvironments = {
+ data: {
+ project: {
+ __typename: 'Project',
+ id: 1,
+ environments: defaultEnvs,
+ },
+ },
+};
+
+export const mockProjectVariables = {
+ data: {
+ project: {
+ __typename: 'Project',
+ id: 1,
+ ciVariables: createDefaultVars(),
+ },
+ },
+};
+
+export const mockGroupEnvironments = {
+ data: {
+ group: {
+ __typename: 'Group',
+ id: 1,
+ environments: defaultEnvs,
+ },
+ },
+};
+
+export const mockGroupVariables = {
+ data: {
+ group: {
+ __typename: 'Group',
+ id: 1,
+ ciVariables: createDefaultVars(),
+ },
+ },
+};
+
+export const mockAdminVariables = {
+ data: {
+ ciVariables: createDefaultVars({ withScope: false }),
+ },
+};
+
+export const newVariable = {
+ id: 3,
+ environmentScope: 'new',
+ key: 'AWS_RANDOM_THING',
+ masked: true,
+ protected: false,
+ value: 'devops',
+ variableType: variableTypes.variableType,
+};
diff --git a/spec/frontend/ci_variable_list/utils_spec.js b/spec/frontend/ci_variable_list/utils_spec.js
new file mode 100644
index 00000000000..1676e786515
--- /dev/null
+++ b/spec/frontend/ci_variable_list/utils_spec.js
@@ -0,0 +1,68 @@
+import {
+ createJoinedEnvironments,
+ convertEnvironmentScope,
+ mapEnvironmentNames,
+} from '~/ci_variable_list/utils';
+import { allEnvironments } from '~/ci_variable_list/constants';
+
+describe('utils', () => {
+ const environments = ['dev', 'prod'];
+
+ describe('createJoinedEnvironments', () => {
+ it('returns only `environments` if `variables` argument is undefined', () => {
+ const variables = undefined;
+
+ expect(createJoinedEnvironments(variables, environments)).toEqual(environments);
+ });
+
+ it('returns a list of environments and environment scopes taken from variables in alphabetical order', () => {
+ const envScope1 = 'new1';
+ const envScope2 = 'new2';
+
+ const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }];
+
+ expect(createJoinedEnvironments(variables, environments)).toEqual([
+ environments[0],
+ envScope1,
+ envScope2,
+ environments[1],
+ ]);
+ });
+
+ it('removes duplicate environments', () => {
+ const envScope1 = environments[0];
+ const envScope2 = 'new2';
+
+ const variables = [{ environmentScope: envScope1 }, { environmentScope: envScope2 }];
+
+ expect(createJoinedEnvironments(variables, environments)).toEqual([
+ environments[0],
+ envScope2,
+ environments[1],
+ ]);
+ });
+ });
+
+ describe('convertEnvironmentScope', () => {
+ it('converts the * to the `All environments` text', () => {
+ expect(convertEnvironmentScope('*')).toBe(allEnvironments.text);
+ });
+
+ it('returns the environment as is if not the *', () => {
+ expect(convertEnvironmentScope('prod')).toBe('prod');
+ });
+ });
+
+ describe('mapEnvironmentNames', () => {
+ const envName = 'dev';
+ const envName2 = 'prod';
+
+ const nodes = [
+ { name: envName, otherProp: {} },
+ { name: envName2, otherProp: {} },
+ ];
+ it('flatten a nodes array with only their names', () => {
+ expect(mapEnvironmentNames(nodes)).toEqual([envName, envName2]);
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/remark_markdown_processing_spec.js b/spec/frontend/content_editor/remark_markdown_processing_spec.js
index e1de2823726..ca552644258 100644
--- a/spec/frontend/content_editor/remark_markdown_processing_spec.js
+++ b/spec/frontend/content_editor/remark_markdown_processing_spec.js
@@ -1170,7 +1170,7 @@ _world_.
const trimmed = markdown.trim();
const document = await deserialize(trimmed);
- expect(expectedDoc).not.toBeFalsy();
+ expect(expectedDoc).not.toBe(false);
expect(document.toJSON()).toEqual(expectedDoc.toJSON());
expect(serialize(document)).toEqual(expectedMarkdown ?? trimmed);
},
diff --git a/spec/frontend/labels/labels_select_spec.js b/spec/frontend/labels/labels_select_spec.js
index f6e280564cc..63f7c725bc7 100644
--- a/spec/frontend/labels/labels_select_spec.js
+++ b/spec/frontend/labels/labels_select_spec.js
@@ -101,6 +101,12 @@ describe('LabelsSelect', () => {
expect($labelEl.find('a').attr('data-html')).toBe('true');
});
+ it('generated label item template has correct title for tooltip', () => {
+ expect($labelEl.find('a').attr('title')).toBe(
+ "<span class='font-weight-bold scoped-label-tooltip-title'>Scoped label</span><br>Foobar",
+ );
+ });
+
it('generated label item template has correct label styles and classes', () => {
expect($labelEl.find('span.gl-label-text').attr('style')).toBe(
`background-color: ${label.color};`,
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index 312e4f636c3..2c6b603197d 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -348,15 +348,13 @@ describe('URL utility', () => {
describe('urlContainsSha', () => {
it('returns true when there is a valid 40-character SHA1 hash in the URL', () => {
shas.valid.forEach((sha) => {
- expect(
- urlUtils.urlContainsSha({ url: `http://urlstuff/${sha}/moreurlstuff` }),
- ).toBeTruthy();
+ expect(urlUtils.urlContainsSha({ url: `http://urlstuff/${sha}/moreurlstuff` })).toBe(true);
});
});
it('returns false when there is not a valid 40-character SHA1 hash in the URL', () => {
shas.invalid.forEach((str) => {
- expect(urlUtils.urlContainsSha({ url: `http://urlstuff/${str}/moreurlstuff` })).toBeFalsy();
+ expect(urlUtils.urlContainsSha({ url: `http://urlstuff/${str}/moreurlstuff` })).toBe(false);
});
});
});
@@ -813,13 +811,13 @@ describe('URL utility', () => {
});
it('should compare against the window location if no compare value is provided', () => {
- expect(urlUtils.urlIsDifferent('different')).toBeTruthy();
- expect(urlUtils.urlIsDifferent(current)).toBeFalsy();
+ expect(urlUtils.urlIsDifferent('different')).toBe(true);
+ expect(urlUtils.urlIsDifferent(current)).toBe(false);
});
it('should use the provided compare value', () => {
- expect(urlUtils.urlIsDifferent('different', current)).toBeTruthy();
- expect(urlUtils.urlIsDifferent(current, current)).toBeFalsy();
+ expect(urlUtils.urlIsDifferent('different', current)).toBe(true);
+ expect(urlUtils.urlIsDifferent(current, current)).toBe(false);
});
});
diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js
index 3034037fb1d..4fcecc3a307 100644
--- a/spec/frontend/projects/project_new_spec.js
+++ b/spec/frontend/projects/project_new_spec.js
@@ -1,6 +1,7 @@
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import { TEST_HOST } from 'helpers/test_constants';
import projectNew from '~/projects/project_new';
+import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
describe('New Project', () => {
let $projectImportUrl;
@@ -12,21 +13,27 @@ describe('New Project', () => {
beforeEach(() => {
setHTMLFixture(`
- <div class='toggle-import-form'>
- <div class='import-url-data'>
- <div class="form-group">
- <input id="project_import_url" />
- </div>
- <div id="import-url-auth-method">
- <div class="form-group">
- <input id="project-import-url-user" />
+ <div class="tab-pane active">
+ <div class='toggle-import-form'>
+ <form id="new_project">
+ <div class='import-url-data'>
+ <div class="form-group">
+ <input id="project_import_url" />
+ </div>
+ <div id="import-url-auth-method">
+ <div class="form-group">
+ <input id="project-import-url-user" />
+ </div>
+ <div class="form-group">
+ <input id="project_import_url_password" />
+ </div>
+ </div>
+ <input id="project_name" />
+ <input id="project_path" />
</div>
- <div class="form-group">
- <input id="project_import_url_password" />
- </div>
- </div>
- <input id="project_name" />
- <input id="project_path" />
+ <div class="js-user-readme-repo"></div>
+ <button class="js-create-project-button"/>
+ </form>
</div>
</div>
`);
@@ -45,6 +52,38 @@ describe('New Project', () => {
el.value = value;
};
+ describe('tracks manual path input', () => {
+ let trackingSpy;
+
+ beforeEach(() => {
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+ projectNew.bindEvents();
+ $projectPath.oldInputValue = '_old_value_';
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks the event', () => {
+ $projectPath.value = '_new_value_';
+
+ triggerEvent($projectPath, 'blur');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'user_input_path_slug', {
+ label: 'new_project_form',
+ });
+ });
+
+ it('does not track the event when there has been no change', () => {
+ $projectPath.value = '_old_value_';
+
+ triggerEvent($projectPath, 'blur');
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+ });
+ });
+
describe('deriveProjectPathFromUrl', () => {
const dummyImportUrl = `${TEST_HOST}/dummy/import/url.git`;
diff --git a/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js b/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js
index f0106914674..193a16bae8d 100644
--- a/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/frontend/vue_merge_request_widget/components/mr_widget_memory_usage_spec.js
@@ -80,20 +80,20 @@ describe('MemoryUsage', () => {
it('should have default data', () => {
const data = MemoryUsage.data();
- expect(Array.isArray(data.memoryMetrics)).toBeTruthy();
+ expect(Array.isArray(data.memoryMetrics)).toBe(true);
expect(data.memoryMetrics.length).toBe(0);
expect(typeof data.deploymentTime).toBe('number');
expect(data.deploymentTime).toBe(0);
expect(typeof data.hasMetrics).toBe('boolean');
- expect(data.hasMetrics).toBeFalsy();
+ expect(data.hasMetrics).toBe(false);
expect(typeof data.loadFailed).toBe('boolean');
- expect(data.loadFailed).toBeFalsy();
+ expect(data.loadFailed).toBe(false);
expect(typeof data.loadingMetrics).toBe('boolean');
- expect(data.loadingMetrics).toBeTruthy();
+ expect(data.loadingMetrics).toBe(true);
expect(typeof data.backOffRequestCounter).toBe('number');
expect(data.backOffRequestCounter).toBe(0);
@@ -144,7 +144,7 @@ describe('MemoryUsage', () => {
vm.computeGraphData(metrics, deployment_time);
const { hasMetrics, memoryMetrics, deploymentTime, memoryFrom, memoryTo } = vm;
- expect(hasMetrics).toBeTruthy();
+ expect(hasMetrics).toBe(true);
expect(memoryMetrics.length).toBeGreaterThan(0);
expect(deploymentTime).toEqual(deployment_time);
expect(memoryFrom).toEqual('9.13');
@@ -171,7 +171,7 @@ describe('MemoryUsage', () => {
describe('template', () => {
it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-memory-usage')).toBeTruthy();
+ expect(el.classList.contains('mr-memory-usage')).toBe(true);
expect(el.querySelector('.js-usage-info')).toBeDefined();
});
diff --git a/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
index f113ffcd0a7..41fe5fbdbbd 100644
--- a/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/base_importer_spec.rb
@@ -4,10 +4,10 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::BaseImporter do
let(:project) { instance_double('Project') }
- let(:user_finder) { instance_double('Gitlab::GithubImport::UserFinder') }
+ let(:client) { instance_double('Gitlab::GithubImport::Client') }
let(:issue_event) { instance_double('Gitlab::GithubImport::Representation::IssueEvent') }
let(:importer_class) { Class.new(described_class) }
- let(:importer_instance) { importer_class.new(project, user_finder) }
+ let(:importer_instance) { importer_class.new(project, client) }
describe '#execute' do
it { expect { importer_instance.execute(issue_event) }.to raise_error(NotImplementedError) }
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
index a1918dd0da8..2f6f727dc38 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_assignee_spec.rb
@@ -3,14 +3,13 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:assignee) { create(:user) }
let_it_be(:assigner) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
@@ -22,7 +21,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
'created_at' => '2022-04-26 18:30:53 UTC',
'assigner' => { 'id' => assigner.id, 'login' => assigner.username },
'assignee' => { 'id' => assignee.id, 'login' => assignee.username },
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -70,8 +69,13 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedAssignee do
describe '#execute' do
before do
- allow(user_finder).to receive(:find).with(assignee.id, assignee.username).and_return(assignee.id)
- allow(user_finder).to receive(:find).with(assigner.id, assigner.username).and_return(assigner.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(assignee.id, assignee.username).and_return(assignee.id)
+ allow(finder).to receive(:find).with(assigner.id, assigner.username).and_return(assigner.id)
+ end
end
context 'when importing an assigned event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
index 98a8daf1653..e21672aa430 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_label_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let!(:label) { create(:label, project: project) }
@@ -21,7 +20,8 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
'commit_id' => nil,
'label_title' => label.title,
'issue_db_id' => issue.id,
- 'created_at' => '2022-04-26 18:30:53 UTC'
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -45,7 +45,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedLabel do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(label.id)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
context 'when importing a labeled event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
index a5852c967df..2687627fc23 100644
--- a/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/changed_milestone_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let!(:milestone) { create(:milestone, project: project) }
@@ -21,7 +20,8 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
'commit_id' => nil,
'milestone_title' => milestone.title,
'issue_db_id' => issue.id,
- 'created_at' => '2022-04-26 18:30:53 UTC'
+ 'created_at' => '2022-04-26 18:30:53 UTC',
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -47,7 +47,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::ChangedMilestone do
describe '#execute' do
before do
allow(Gitlab::Cache::Import::Caching).to receive(:read_integer).and_return(milestone.id)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
context 'when importing a milestoned event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
index 749c52a215e..9a49d80a8bb 100644
--- a/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/closed_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:commit_id) { nil }
@@ -22,7 +21,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
'event' => 'closed',
'created_at' => '2022-04-26 18:30:53 UTC',
'commit_id' => commit_id,
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -48,7 +47,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Closed do
end
before do
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected event and state event' do
diff --git a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
index fd2e564d1b1..68e001c7364 100644
--- a/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/cross_referenced_spec.rb
@@ -3,14 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_gitlab_redis_cache do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
- let(:sawyer_stub) { Struct.new(:iid, :issuable_type, keyword_init: true) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue_iid) { 999 }
let(:issue) { create(:issue, project: project, iid: issue_iid) }
@@ -32,7 +30,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
}
},
'created_at' => '2022-04-26 18:30:53 UTC',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -41,7 +39,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
{
system: true,
noteable_type: Issue.name,
- noteable_id: issue_event.issue_db_id,
+ noteable_id: issue.id,
project_id: project.id,
author_id: user.id,
note: expected_note_body,
@@ -53,10 +51,13 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
let(:expected_note_body) { "mentioned in issue ##{referenced_in.iid}" }
before do
- other_issue_resource = sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'Issue')
- Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
- .cache_database_id(referenced_in.iid)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(referenced_in.iid)
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
@@ -75,11 +76,13 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::CrossReferenced, :clean_g
let(:expected_note_body) { "mentioned in merge request !#{referenced_in.iid}" }
before do
- other_issue_resource =
- sawyer_stub.new(iid: referenced_in.iid, issuable_type: 'MergeRequest')
- Gitlab::GithubImport::IssuableFinder.new(project, other_issue_resource)
- .cache_database_id(referenced_in.iid)
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(referenced_in.iid)
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
diff --git a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
index 8acf82af40c..316ea798965 100644
--- a/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/renamed_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
@@ -11,7 +11,6 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
let(:issue) { create(:issue, project: project) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue_event) do
Gitlab::GithubImport::Representation::IssueEvent.from_json_hash(
'id' => 6501124486,
@@ -21,7 +20,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
'created_at' => '2022-04-26 18:30:53 UTC',
'old_title' => 'old title',
'new_title' => 'new title',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -48,7 +47,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Renamed do
describe '#execute' do
before do
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected note' do
diff --git a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
index 39b8809dfa4..2461dbb9701 100644
--- a/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/events/reopened_spec.rb
@@ -3,13 +3,12 @@
require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_failures do
- subject(:importer) { described_class.new(project, user_finder) }
+ subject(:importer) { described_class.new(project, client) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
- let(:user_finder) { Gitlab::GithubImport::UserFinder.new(project, client) }
let(:issue) { create(:issue, project: project) }
let(:issue_event) do
@@ -20,7 +19,7 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
'actor' => { 'id' => user.id, 'login' => user.username },
'event' => 'reopened',
'created_at' => '2022-04-26 18:30:53 UTC',
- 'issue_db_id' => issue.id
+ 'issue' => { 'number' => issue.iid }
)
end
@@ -45,7 +44,12 @@ RSpec.describe Gitlab::GithubImport::Importer::Events::Reopened, :aggregate_fail
end
before do
- allow(user_finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ allow_next_instance_of(Gitlab::GithubImport::IssuableFinder) do |finder|
+ allow(finder).to receive(:database_id).and_return(issue.id)
+ end
+ allow_next_instance_of(Gitlab::GithubImport::UserFinder) do |finder|
+ allow(finder).to receive(:find).with(user.id, user.username).and_return(user.id)
+ end
end
it 'creates expected event and state event' do
diff --git a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
index fee7c2708a4..33d5fbf13a0 100644
--- a/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/issue_event_importer_spec.rb
@@ -33,7 +33,7 @@ RSpec.describe Gitlab::GithubImport::Importer::IssueEventImporter, :clean_gitlab
specific_importer = double(importer_class.name) # rubocop:disable RSpec/VerifiedDoubles
expect(importer_class)
- .to receive(:new).with(project, anything)
+ .to receive(:new).with(project, client)
.and_return(specific_importer)
expect(specific_importer).to receive(:execute).with(issue_event)
diff --git a/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb
new file mode 100644
index 00000000000..8d4c1b01e50
--- /dev/null
+++ b/spec/lib/gitlab/github_import/importer/issue_events_importer_spec.rb
@@ -0,0 +1,122 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::GithubImport::Importer::IssueEventsImporter do
+ subject(:importer) { described_class.new(project, client, parallel: parallel) }
+
+ let(:project) { instance_double(Project, id: 4, import_source: 'foo/bar') }
+ let(:client) { instance_double(Gitlab::GithubImport::Client) }
+
+ let(:parallel) { true }
+ let(:issue_event) do
+ struct = Struct.new(
+ :id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :rename, :milestone,
+ :source, :assignee, :assigner, :issue, :created_at, :performed_via_github_app,
+ keyword_init: true
+ )
+ struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
+ end
+
+ describe '#parallel?' do
+ context 'when running in parallel mode' do
+ it { expect(importer).to be_parallel }
+ end
+
+ context 'when running in sequential mode' do
+ let(:parallel) { false }
+
+ it { expect(importer).not_to be_parallel }
+ end
+ end
+
+ describe '#execute' do
+ context 'when running in parallel mode' do
+ it 'imports events in parallel' do
+ expect(importer).to receive(:parallel_import)
+
+ importer.execute
+ end
+ end
+
+ context 'when running in sequential mode' do
+ let(:parallel) { false }
+
+ it 'imports notes in sequence' do
+ expect(importer).to receive(:sequential_import)
+
+ importer.execute
+ end
+ end
+ end
+
+ describe '#sequential_import' do
+ let(:parallel) { false }
+
+ it 'imports each event in sequence' do
+ event_importer = instance_double(Gitlab::GithubImport::Importer::IssueEventImporter)
+
+ allow(importer).to receive(:each_object_to_import).and_yield(issue_event)
+
+ expect(Gitlab::GithubImport::Importer::IssueEventImporter)
+ .to receive(:new)
+ .with(
+ an_instance_of(Gitlab::GithubImport::Representation::IssueEvent),
+ project,
+ client
+ )
+ .and_return(event_importer)
+
+ expect(event_importer).to receive(:execute)
+
+ importer.sequential_import
+ end
+ end
+
+ describe '#parallel_import' do
+ it 'imports each note in parallel' do
+ allow(importer).to receive(:each_object_to_import).and_yield(issue_event)
+
+ expect(Gitlab::GithubImport::ImportIssueEventWorker).to receive(:bulk_perform_in).with(
+ 1.second, [
+ [project.id, an_instance_of(Hash), an_instance_of(String)]
+ ], batch_size: 1000, batch_delay: 1.minute
+ )
+
+ waiter = importer.parallel_import
+
+ expect(waiter).to be_an_instance_of(Gitlab::JobWaiter)
+ expect(waiter.jobs_remaining).to eq(1)
+ end
+ end
+
+ describe '#importer_class' do
+ it { expect(importer.importer_class).to eq Gitlab::GithubImport::Importer::IssueEventImporter }
+ end
+
+ describe '#representation_class' do
+ it { expect(importer.representation_class).to eq Gitlab::GithubImport::Representation::IssueEvent }
+ end
+
+ describe '#sidekiq_worker_class' do
+ it { expect(importer.sidekiq_worker_class).to eq Gitlab::GithubImport::ImportIssueEventWorker }
+ end
+
+ describe '#object_type' do
+ it { expect(importer.object_type).to eq :issue_event }
+ end
+
+ describe '#collection_method' do
+ it { expect(importer.collection_method).to eq :repository_issue_events }
+ end
+
+ describe '#id_for_already_imported_cache' do
+ it 'returns the ID of the given note' do
+ expect(importer.id_for_already_imported_cache(issue_event)).to eq(issue_event.id)
+ end
+ end
+
+ describe '#collection_options' do
+ it { expect(importer.collection_options).to eq({}) }
+ end
+end
diff --git a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
index 087faeffe02..bb1ee79ad93 100644
--- a/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/single_endpoint_issue_events_importer_spec.rb
@@ -53,7 +53,7 @@ RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
describe '#each_object_to_import', :clean_gitlab_redis_cache do
let(:issue_event) do
- struct = Struct.new(:id, :event, :created_at, :issue_db_id, keyword_init: true)
+ struct = Struct.new(:id, :event, :created_at, :issue, keyword_init: true)
struct.new(id: rand(10), event: 'closed', created_at: '2022-04-26 18:30:53 UTC')
end
@@ -81,7 +81,7 @@ RSpec.describe Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter
counter = 0
subject.each_object_to_import do |object|
expect(object).to eq issue_event
- expect(issue_event.issue_db_id).to eq issue.id
+ expect(issue_event.issue['number']).to eq issue.iid
counter += 1
end
expect(counter).to eq 1
diff --git a/spec/lib/gitlab/github_import/issuable_finder_spec.rb b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
index 3afd006109b..d550f15e8c5 100644
--- a/spec/lib/gitlab/github_import/issuable_finder_spec.rb
+++ b/spec/lib/gitlab/github_import/issuable_finder_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::IssuableFinder, :clean_gitlab_redis_cache do
let(:project) { double(:project, id: 4, group: nil) }
let(:issue) do
- double(:issue, issuable_type: MergeRequest, iid: 1)
+ double(:issue, issuable_type: MergeRequest, issuable_id: 1)
end
let(:finder) { described_class.new(project, issue) }
diff --git a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
index cf796b55b14..d3a98035e73 100644
--- a/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/issue_event_spec.rb
@@ -25,8 +25,8 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
expect(issue_event.source).to eq({ type: 'issue', id: 123456 })
end
- it 'includes the issue_db_id' do
- expect(issue_event.issue_db_id).to eq(100500)
+ it 'includes the issue data' do
+ expect(issue_event.issue).to eq({ number: 2, pull_request: pull_request })
end
context 'when actor data present' do
@@ -119,6 +119,24 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
end
end
+ describe '#issuable_id' do
+ it 'returns issuable_id' do
+ expect(issue_event.issuable_id).to eq(2)
+ end
+ end
+
+ describe '#issuable_type' do
+ context 'when event related to issue' do
+ it { expect(issue_event.issuable_type).to eq('Issue') }
+ end
+
+ context 'when event related to pull request' do
+ let(:pull_request) { { url: FFaker::Internet.http_url } }
+
+ it { expect(issue_event.issuable_type).to eq('MergeRequest') }
+ end
+ end
+
describe '#github_identifiers' do
it 'returns a hash with needed identifiers' do
expect(issue_event.github_identifiers).to eq({ id: 6501124486 })
@@ -130,7 +148,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:response) do
event_resource = Struct.new(
:id, :node_id, :url, :actor, :event, :commit_id, :commit_url, :label, :rename, :milestone,
- :source, :assignee, :assigner, :issue_db_id, :created_at, :performed_via_github_app,
+ :source, :assignee, :assigner, :issue, :created_at, :performed_via_github_app,
keyword_init: true
)
user_resource = Struct.new(:id, :login, keyword_init: true)
@@ -149,7 +167,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
source: { type: 'issue', id: 123456 },
assignee: with_assignee ? user_resource.new(id: 5, login: 'tom') : nil,
assigner: with_assignee ? user_resource.new(id: 6, login: 'jerry') : nil,
- issue_db_id: 100500,
+ issue: { 'number' => 2, 'pull_request' => pull_request },
created_at: '2022-04-26 18:30:53 UTC',
performed_via_github_app: nil
)
@@ -160,6 +178,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_rename) { true }
let(:with_milestone) { true }
let(:with_assignee) { true }
+ let(:pull_request) { nil }
it_behaves_like 'an IssueEvent' do
let(:issue_event) { described_class.from_api_response(response) }
@@ -185,7 +204,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
'source' => { 'type' => 'issue', 'id' => 123456 },
'assignee' => (with_assignee ? { 'id' => 5, 'login' => 'tom' } : nil),
'assigner' => (with_assignee ? { 'id' => 6, 'login' => 'jerry' } : nil),
- "issue_db_id" => 100500,
+ 'issue' => { 'number' => 2, 'pull_request' => pull_request },
'created_at' => '2022-04-26 18:30:53 UTC',
'performed_via_github_app' => nil
}
@@ -196,6 +215,7 @@ RSpec.describe Gitlab::GithubImport::Representation::IssueEvent do
let(:with_rename) { true }
let(:with_milestone) { true }
let(:with_assignee) { true }
+ let(:pull_request) { nil }
let(:issue_event) { described_class.from_json_hash(hash) }
end
diff --git a/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
new file mode 100644
index 00000000000..9421561aea4
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_cross_project_authorization_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'when the user cannot read cross project' do |action, params|
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(false)
+ end
+
+ it 'blocks access without a project_id' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'allows access with a project_id' do
+ get action, params: params.merge(project_id: create(:project, :public).id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
new file mode 100644
index 00000000000..6b72988b3e6
--- /dev/null
+++ b/spec/support/shared_examples/controllers/search_external_authorization_service_shared_examples.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples_for 'with external authorization service enabled' do |action, params|
+ include ExternalAuthorizationServiceHelpers
+
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ before do
+ enable_external_authorization_service_check
+ end
+
+ it 'renders a 403 when no project is given' do
+ get action, params: params
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'renders a 200 when a project was set' do
+ get action, params: params.merge(project_id: project.id)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+end
diff --git a/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
index b3c6a48767c..932152c0764 100644
--- a/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb
@@ -8,37 +8,66 @@ RSpec.describe Gitlab::GithubImport::Stage::ImportIssueEventsWorker do
let(:project) { create(:project) }
let!(:group) { create(:group, projects: [project]) }
let(:feature_flag_state) { [group] }
+ let(:single_endpoint_feature_flag_state) { [group] }
describe '#import' do
let(:importer) { instance_double('Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter') }
let(:client) { instance_double('Gitlab::GithubImport::Client') }
before do
+ stub_feature_flags(github_importer_single_endpoint_issue_events_import: single_endpoint_feature_flag_state)
stub_feature_flags(github_importer_issue_events_import: feature_flag_state)
end
- it 'imports all the issue events' do
- waiter = Gitlab::JobWaiter.new(2, '123')
+ context 'when single endpoint feature flag enabled' do
+ it 'imports all the issue events' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
- expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter)
- .to receive(:new)
- .with(project, client)
- .and_return(importer)
+ expect(Gitlab::GithubImport::Importer::IssueEventsImporter).not_to receive(:new)
+ expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
- expect(importer).to receive(:execute).and_return(waiter)
+ expect(importer).to receive(:execute).and_return(waiter)
- expect(Gitlab::GithubImport::AdvanceStageWorker)
- .to receive(:perform_async)
- .with(project.id, { '123' => 2 }, :notes)
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :notes)
- worker.import(client, project)
+ worker.import(client, project)
+ end
+ end
+
+ context 'when import issue events feature flag enabled' do
+ let(:single_endpoint_feature_flag_state) { false }
+
+ it 'imports the issue events partly' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter).not_to receive(:new)
+ expect(Gitlab::GithubImport::Importer::IssueEventsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
+
+ expect(importer).to receive(:execute).and_return(waiter)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :notes)
+
+ worker.import(client, project)
+ end
end
- context 'when feature flag is disabled' do
+ context 'when feature flags are disabled' do
let(:feature_flag_state) { false }
+ let(:single_endpoint_feature_flag_state) { false }
it 'skips issue events import and calls next stage' do
expect(Gitlab::GithubImport::Importer::SingleEndpointIssueEventsImporter).not_to receive(:new)
+ expect(Gitlab::GithubImport::Importer::IssueEventsImporter).not_to receive(:new)
expect(Gitlab::GithubImport::AdvanceStageWorker).to receive(:perform_async).with(project.id, {}, :notes)
worker.import(client, project)