summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-08 12:09:53 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-08 12:09:53 +0000
commit148b75b329294f6b6ae409bbf8d70590e63c6bc9 (patch)
tree30918a97e353067ff9c99e04fb7e296305d130b7
parent707742e59ca57d1f2ea00d65fa35a7b9a5ded398 (diff)
downloadgitlab-ce-148b75b329294f6b6ae409bbf8d70590e63c6bc9.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/diffs/store/actions.js4
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js9
-rw-r--r--app/assets/javascripts/registry/settings/components/registry_settings_app.vue6
-rw-r--r--app/assets/javascripts/registry/settings/components/settings_form.vue4
-rw-r--r--app/assets/javascripts/registry/settings/constants.js36
-rw-r--r--app/assets/javascripts/registry/settings/utils.js (renamed from app/assets/javascripts/registry/shared/utils.js)15
-rw-r--r--app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue258
-rw-r--r--app/assets/javascripts/registry/shared/constants.js69
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue2
-rw-r--r--app/assets/stylesheets/fontawesome_custom.scss4
-rw-r--r--app/assets/stylesheets/framework/buttons.scss10
-rw-r--r--app/assets/stylesheets/pages/issues.scss8
-rw-r--r--app/assets/stylesheets/pages/projects.scss9
-rw-r--r--app/models/ci/build_dependencies.rb78
-rw-r--r--app/models/identity.rb3
-rw-r--r--app/models/user.rb16
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb15
-rw-r--r--changelogs/unreleased/272983-fix-security-mr-widget-forks.yml5
-rw-r--r--changelogs/unreleased/feature-git-crowd-auth.yml5
-rw-r--r--changelogs/unreleased/include-actual-limit-in-pipeline-errors.yml5
-rw-r--r--changelogs/unreleased/sy-process-prometheus-alerts-through-http-integrations.yml5
-rw-r--r--config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml8
-rw-r--r--config/feature_flags/development/saas_add_seats_button.yml (renamed from config/feature_flags/development/cd_auto_rollback.yml)10
-rw-r--r--doc/administration/auth/crowd.md3
-rw-r--r--doc/development/database_review.md55
-rw-r--r--doc/user/group/saml_sso/index.md10
-rw-r--r--doc/user/project/import/jira.md6
-rw-r--r--doc/user/search/advanced_global_search.md6
-rw-r--r--lib/gitlab/auth/crowd/authentication.rb35
-rw-r--r--lib/gitlab/auth/ldap/user.rb14
-rw-r--r--lib/gitlab/auth/o_auth/provider.rb2
-rw-r--r--lib/gitlab/auth/o_auth/user.rb19
-rw-r--r--lib/gitlab/ci/features.rb4
-rw-r--r--locale/gitlab.pot39
-rw-r--r--spec/factories/alert_management/http_integrations.rb4
-rw-r--r--spec/frontend/diffs/store/actions_spec.js2
-rw-r--r--spec/frontend/diffs/store/mutations_spec.js22
-rw-r--r--spec/frontend/registry/settings/__snapshots__/utils_spec.js.snap (renamed from spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap)0
-rw-r--r--spec/frontend/registry/settings/components/expiration_input_spec.js2
-rw-r--r--spec/frontend/registry/settings/components/registry_settings_app_spec.js2
-rw-r--r--spec/frontend/registry/settings/components/settings_form_spec.js2
-rw-r--r--spec/frontend/registry/settings/utils_spec.js (renamed from spec/frontend/registry/shared/utils_spec.js)2
-rw-r--r--spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap148
-rw-r--r--spec/frontend/registry/shared/components/expiration_policy_fields_spec.js202
-rw-r--r--spec/frontend/registry/shared/mock_data.js12
-rw-r--r--spec/lib/gitlab/auth/crowd/authentication_spec.rb48
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb17
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb17
-rw-r--r--spec/models/ci/build_dependencies_spec.rb198
-rw-r--r--spec/models/identity_spec.rb30
-rw-r--r--spec/models/user_spec.rb22
-rw-r--r--spec/services/projects/prometheus/alerts/notify_service_spec.rb13
53 files changed, 637 insertions, 885 deletions
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index ee319990290..8c72971682d 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -185,11 +185,11 @@ export const fetchDiffFilesMeta = ({ commit, state }) => {
.get(mergeUrlParams(urlParams, state.endpointMetadata))
.then(({ data }) => {
const strippedData = { ...data };
-
delete strippedData.diff_files;
+
commit(types.SET_LOADING, false);
commit(types.SET_MERGE_REQUEST_DIFFS, data.merge_request_diffs || []);
- commit(types.SET_DIFF_DATA, strippedData);
+ commit(types.SET_DIFF_METADATA, strippedData);
worker.postMessage(prepareDiffData(data, state.diffFiles));
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index 25184028799..30097239aaa 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -3,7 +3,7 @@ export const SET_LOADING = 'SET_LOADING';
export const SET_BATCH_LOADING = 'SET_BATCH_LOADING';
export const SET_RETRIEVING_BATCHES = 'SET_RETRIEVING_BATCHES';
-export const SET_DIFF_DATA = 'SET_DIFF_DATA';
+export const SET_DIFF_METADATA = 'SET_DIFF_METADATA';
export const SET_DIFF_DATA_BATCH = 'SET_DIFF_DATA_BATCH';
export const SET_DIFF_FILES = 'SET_DIFF_FILES';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 69ae3f705e3..90940d82226 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -66,17 +66,10 @@ export default {
updateDiffFilesInState(state, files);
},
- [types.SET_DIFF_DATA](state, data) {
- let files = state.diffFiles;
-
- if (window.location.search.indexOf('diff_id') !== -1 && data.diff_files) {
- files = prepareDiffData(data, files);
- }
-
+ [types.SET_DIFF_METADATA](state, data) {
Object.assign(state, {
...convertObjectPropsToCamelCase(data),
});
- updateDiffFilesInState(state, files);
},
[types.SET_DIFF_DATA_BATCH](state, data) {
diff --git a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
index e236834d8e1..ac6a0871153 100644
--- a/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
+++ b/app/assets/javascripts/registry/settings/components/registry_settings_app.vue
@@ -2,16 +2,16 @@
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import { isEqual, get, isEmpty } from 'lodash';
import expirationPolicyQuery from '../graphql/queries/get_expiration_policy.graphql';
-import { FETCH_SETTINGS_ERROR_MESSAGE } from '../../shared/constants';
-
-import SettingsForm from './settings_form.vue';
import {
+ FETCH_SETTINGS_ERROR_MESSAGE,
UNAVAILABLE_FEATURE_TITLE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
UNAVAILABLE_USER_FEATURE_TEXT,
UNAVAILABLE_ADMIN_FEATURE_TEXT,
} from '../constants';
+import SettingsForm from './settings_form.vue';
+
export default {
components: {
SettingsForm,
diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue
index 3eab7e6d038..c46c633d274 100644
--- a/app/assets/javascripts/registry/settings/components/settings_form.vue
+++ b/app/assets/javascripts/registry/settings/components/settings_form.vue
@@ -4,8 +4,6 @@ import Tracking from '~/tracking';
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
-} from '~/registry/shared/constants';
-import {
SET_CLEANUP_POLICY_BUTTON,
KEEP_HEADER_TEXT,
KEEP_INFO_TEXT,
@@ -21,7 +19,7 @@ import {
CADENCE_LABEL,
EXPIRATION_POLICY_FOOTER_NOTE,
} from '~/registry/settings/constants';
-import { formOptionsGenerator } from '~/registry/shared/utils';
+import { formOptionsGenerator } from '~/registry/settings/utils';
import updateContainerExpirationPolicyMutation from '~/registry/settings/graphql/mutations/update_container_expiration_policy.graphql';
import { updateContainerExpirationPolicy } from '~/registry/settings/graphql/utils/cache_update';
import ExpirationDropdown from './expiration_dropdown.vue';
diff --git a/app/assets/javascripts/registry/settings/constants.js b/app/assets/javascripts/registry/settings/constants.js
index 1dd533ce665..21c54299632 100644
--- a/app/assets/javascripts/registry/settings/constants.js
+++ b/app/assets/javascripts/registry/settings/constants.js
@@ -52,4 +52,40 @@ export const EXPIRATION_POLICY_FOOTER_NOTE = s__(
'ContainerRegistry|Note: Any policy update will result in a change to the scheduled run date and time',
);
+export const KEEP_N_OPTIONS = [
+ { key: 'ONE_TAG', variable: 1, default: false },
+ { key: 'FIVE_TAGS', variable: 5, default: false },
+ { key: 'TEN_TAGS', variable: 10, default: true },
+ { key: 'TWENTY_FIVE_TAGS', variable: 25, default: false },
+ { key: 'FIFTY_TAGS', variable: 50, default: false },
+ { key: 'ONE_HUNDRED_TAGS', variable: 100, default: false },
+];
+
+export const CADENCE_OPTIONS = [
+ { key: 'EVERY_DAY', label: __('Every day'), default: true },
+ { key: 'EVERY_WEEK', label: __('Every week'), default: false },
+ { key: 'EVERY_TWO_WEEKS', label: __('Every two weeks'), default: false },
+ { key: 'EVERY_MONTH', label: __('Every month'), default: false },
+ { key: 'EVERY_THREE_MONTHS', label: __('Every three months'), default: false },
+];
+
+export const OLDER_THAN_OPTIONS = [
+ { key: 'SEVEN_DAYS', variable: 7, default: false },
+ { key: 'FOURTEEN_DAYS', variable: 14, default: false },
+ { key: 'THIRTY_DAYS', variable: 30, default: false },
+ { key: 'NINETY_DAYS', variable: 90, default: true },
+];
+
+export const FETCH_SETTINGS_ERROR_MESSAGE = s__(
+ 'ContainerRegistry|Something went wrong while fetching the cleanup policy.',
+);
+
+export const UPDATE_SETTINGS_ERROR_MESSAGE = s__(
+ 'ContainerRegistry|Something went wrong while updating the cleanup policy.',
+);
+
+export const UPDATE_SETTINGS_SUCCESS_MESSAGE = s__(
+ 'ContainerRegistry|Cleanup policy successfully saved.',
+);
+
export const NAME_REGEX_LENGTH = 255;
diff --git a/app/assets/javascripts/registry/shared/utils.js b/app/assets/javascripts/registry/settings/utils.js
index 5c8c505f835..51b4fb6bdb8 100644
--- a/app/assets/javascripts/registry/shared/utils.js
+++ b/app/assets/javascripts/registry/settings/utils.js
@@ -6,21 +6,6 @@ export const findDefaultOption = options => {
return item ? item.key : null;
};
-export const mapComputedToEvent = (list, root) => {
- const result = {};
- list.forEach(e => {
- result[e] = {
- get() {
- return this[root][e];
- },
- set(value) {
- this.$emit('input', { newValue: { ...this[root], [e]: value }, modified: e });
- },
- };
- });
- return result;
-};
-
export const olderThanTranslationGenerator = variable => n__('%d day', '%d days', variable);
export const keepNTranslationGenerator = variable =>
diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue
deleted file mode 100644
index 2b8e9f6ff64..00000000000
--- a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue
+++ /dev/null
@@ -1,258 +0,0 @@
-<script>
-import { uniqueId } from 'lodash';
-import { GlFormGroup, GlToggle, GlFormSelect, GlFormTextarea, GlSprintf } from '@gitlab/ui';
-import {
- NAME_REGEX_LENGTH,
- ENABLED_TEXT,
- DISABLED_TEXT,
- TEXT_AREA_INVALID_FEEDBACK,
- EXPIRATION_INTERVAL_LABEL,
- EXPIRATION_SCHEDULE_LABEL,
- KEEP_N_LABEL,
- NAME_REGEX_LABEL,
- NAME_REGEX_PLACEHOLDER,
- NAME_REGEX_DESCRIPTION,
- NAME_REGEX_KEEP_LABEL,
- NAME_REGEX_KEEP_PLACEHOLDER,
- NAME_REGEX_KEEP_DESCRIPTION,
- ENABLE_TOGGLE_LABEL,
- ENABLE_TOGGLE_DESCRIPTION,
-} from '../constants';
-import { mapComputedToEvent } from '../utils';
-
-export default {
- components: {
- GlFormGroup,
- GlToggle,
- GlFormSelect,
- GlFormTextarea,
- GlSprintf,
- },
- props: {
- formOptions: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- apiErrors: {
- type: Object,
- required: false,
- default: null,
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
- value: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- labelCols: {
- type: [Number, String],
- required: false,
- default: 3,
- },
- labelAlign: {
- type: String,
- required: false,
- default: 'right',
- },
- },
- i18n: {
- ENABLE_TOGGLE_LABEL,
- ENABLE_TOGGLE_DESCRIPTION,
- },
- selectList: [
- {
- name: 'expiration-policy-interval',
- label: EXPIRATION_INTERVAL_LABEL,
- model: 'olderThan',
- },
- {
- name: 'expiration-policy-schedule',
- label: EXPIRATION_SCHEDULE_LABEL,
- model: 'cadence',
- },
- {
- name: 'expiration-policy-latest',
- label: KEEP_N_LABEL,
- model: 'keepN',
- },
- ],
- textAreaList: [
- {
- name: 'expiration-policy-name-matching',
- label: NAME_REGEX_LABEL,
- model: 'nameRegex',
- placeholder: NAME_REGEX_PLACEHOLDER,
- description: NAME_REGEX_DESCRIPTION,
- },
- {
- name: 'expiration-policy-keep-name',
- label: NAME_REGEX_KEEP_LABEL,
- model: 'nameRegexKeep',
- placeholder: NAME_REGEX_KEEP_PLACEHOLDER,
- description: NAME_REGEX_KEEP_DESCRIPTION,
- },
- ],
- data() {
- return {
- uniqueId: uniqueId(),
- };
- },
- computed: {
- ...mapComputedToEvent(
- ['enabled', 'cadence', 'olderThan', 'keepN', 'nameRegex', 'nameRegexKeep'],
- 'value',
- ),
- policyEnabledText() {
- return this.enabled ? ENABLED_TEXT : DISABLED_TEXT;
- },
- textAreaValidation() {
- const nameRegexErrors = this.apiErrors?.nameRegex || this.validateRegexLength(this.nameRegex);
- const nameKeepRegexErrors =
- this.apiErrors?.nameRegexKeep || this.validateRegexLength(this.nameRegexKeep);
-
- return {
- /*
- * The state has this form:
- * null: gray border, no message
- * true: green border, no message ( because none is configured)
- * false: red border, error message
- * So in this function we keep null if the are no message otherwise we 'invert' the error message
- */
- nameRegex: {
- state: nameRegexErrors === null ? null : !nameRegexErrors,
- message: nameRegexErrors,
- },
- nameRegexKeep: {
- state: nameKeepRegexErrors === null ? null : !nameKeepRegexErrors,
- message: nameKeepRegexErrors,
- },
- };
- },
- fieldsValidity() {
- return (
- this.textAreaValidation.nameRegex.state !== false &&
- this.textAreaValidation.nameRegexKeep.state !== false
- );
- },
- isFormElementDisabled() {
- return !this.enabled || this.isLoading;
- },
- },
- watch: {
- fieldsValidity: {
- immediate: true,
- handler(valid) {
- if (valid) {
- this.$emit('validated');
- } else {
- this.$emit('invalidated');
- }
- },
- },
- },
- methods: {
- validateRegexLength(value) {
- if (!value) {
- return null;
- }
- return value.length <= NAME_REGEX_LENGTH ? '' : TEXT_AREA_INVALID_FEEDBACK;
- },
- idGenerator(id) {
- return `${id}_${this.uniqueId}`;
- },
- updateModel(value, key) {
- this[key] = value;
- },
- },
-};
-</script>
-
-<template>
- <div ref="form-elements" class="gl-line-height-20">
- <gl-form-group
- :id="idGenerator('expiration-policy-toggle-group')"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator('expiration-policy-toggle')"
- :label="$options.i18n.ENABLE_TOGGLE_LABEL"
- >
- <div class="gl-display-flex">
- <gl-toggle
- :id="idGenerator('expiration-policy-toggle')"
- v-model="enabled"
- :disabled="isLoading"
- />
- <span class="gl-mb-3 gl-ml-3 gl-line-height-20">
- <gl-sprintf :message="$options.i18n.ENABLE_TOGGLE_DESCRIPTION">
- <template #toggleStatus>
- <strong>{{ policyEnabledText }}</strong>
- </template>
- </gl-sprintf>
- </span>
- </div>
- </gl-form-group>
-
- <gl-form-group
- v-for="select in $options.selectList"
- :id="idGenerator(`${select.name}-group`)"
- :key="select.name"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator(select.name)"
- :label="select.label"
- >
- <gl-form-select
- :id="idGenerator(select.name)"
- :value="value[select.model]"
- :disabled="isFormElementDisabled"
- @input="updateModel($event, select.model)"
- >
- <option v-for="option in formOptions[select.model]" :key="option.key" :value="option.key">
- {{ option.label }}
- </option>
- </gl-form-select>
- </gl-form-group>
-
- <gl-form-group
- v-for="textarea in $options.textAreaList"
- :id="idGenerator(`${textarea.name}-group`)"
- :key="textarea.name"
- :label-cols="labelCols"
- :label-align="labelAlign"
- :label-for="idGenerator(textarea.name)"
- :state="textAreaValidation[textarea.model].state"
- :invalid-feedback="textAreaValidation[textarea.model].message"
- >
- <template #label>
- <gl-sprintf :message="textarea.label">
- <template #italic="{content}">
- <i>{{ content }}</i>
- </template>
- </gl-sprintf>
- </template>
- <gl-form-textarea
- :id="idGenerator(textarea.name)"
- :value="value[textarea.model]"
- :placeholder="textarea.placeholder"
- :state="textAreaValidation[textarea.model].state"
- :disabled="isFormElementDisabled"
- trim
- @input="updateModel($event, textarea.model)"
- />
- <template #description>
- <span ref="regex-description">
- <gl-sprintf :message="textarea.description">
- <template #code="{content}">
- <code>{{ content }}</code>
- </template>
- </gl-sprintf>
- </span>
- </template>
- </gl-form-group>
- </div>
-</template>
diff --git a/app/assets/javascripts/registry/shared/constants.js b/app/assets/javascripts/registry/shared/constants.js
deleted file mode 100644
index d1e3d93938b..00000000000
--- a/app/assets/javascripts/registry/shared/constants.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { s__, __ } from '~/locale';
-
-export const FETCH_SETTINGS_ERROR_MESSAGE = s__(
- 'ContainerRegistry|Something went wrong while fetching the cleanup policy.',
-);
-
-export const UPDATE_SETTINGS_ERROR_MESSAGE = s__(
- 'ContainerRegistry|Something went wrong while updating the cleanup policy.',
-);
-
-export const UPDATE_SETTINGS_SUCCESS_MESSAGE = s__(
- 'ContainerRegistry|Cleanup policy successfully saved.',
-);
-
-export const NAME_REGEX_LENGTH = 255;
-
-export const ENABLED_TEXT = __('Enabled');
-export const DISABLED_TEXT = __('Disabled');
-
-export const ENABLE_TOGGLE_LABEL = s__('ContainerRegistry|Cleanup policy:');
-export const ENABLE_TOGGLE_DESCRIPTION = s__(
- 'ContainerRegistry|%{toggleStatus} - Tags matching the patterns defined below will be scheduled for deletion',
-);
-
-export const TEXT_AREA_INVALID_FEEDBACK = s__(
- 'ContainerRegistry|The value of this input should be less than 256 characters',
-);
-
-export const EXPIRATION_INTERVAL_LABEL = s__('ContainerRegistry|Expiration interval:');
-export const EXPIRATION_SCHEDULE_LABEL = s__('ContainerRegistry|Expiration schedule:');
-export const KEEP_N_LABEL = s__('ContainerRegistry|Number of tags to retain:');
-export const NAME_REGEX_LABEL = s__(
- 'ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}',
-);
-export const NAME_REGEX_PLACEHOLDER = '';
-export const NAME_REGEX_DESCRIPTION = s__(
- 'ContainerRegistry|Wildcards such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}',
-);
-export const NAME_REGEX_KEEP_LABEL = s__(
- 'ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}',
-);
-export const NAME_REGEX_KEEP_PLACEHOLDER = '';
-export const NAME_REGEX_KEEP_DESCRIPTION = s__(
- 'ContainerRegistry|Wildcards such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported',
-);
-
-export const KEEP_N_OPTIONS = [
- { variable: 1, key: 'ONE_TAG', default: false },
- { variable: 5, key: 'FIVE_TAGS', default: false },
- { variable: 10, key: 'TEN_TAGS', default: true },
- { variable: 25, key: 'TWENTY_FIVE_TAGS', default: false },
- { variable: 50, key: 'FIFTY_TAGS', default: false },
- { variable: 100, key: 'ONE_HUNDRED_TAGS', default: false },
-];
-
-export const CADENCE_OPTIONS = [
- { key: 'EVERY_DAY', label: __('Every day'), default: true },
- { key: 'EVERY_WEEK', label: __('Every week'), default: false },
- { key: 'EVERY_TWO_WEEKS', label: __('Every two weeks'), default: false },
- { key: 'EVERY_MONTH', label: __('Every month'), default: false },
- { key: 'EVERY_THREE_MONTHS', label: __('Every three months'), default: false },
-];
-
-export const OLDER_THAN_OPTIONS = [
- { key: 'SEVEN_DAYS', variable: 7, default: false },
- { key: 'FOURTEEN_DAYS', variable: 14, default: false },
- { key: 'THIRTY_DAYS', variable: 30, default: false },
- { key: 'NINETY_DAYS', variable: 90, default: true },
-];
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 433dcf2e219..d121daf0aa7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -464,7 +464,7 @@ export default {
<security-reports-app
v-if="shouldRenderSecurityReport"
:pipeline-id="mr.pipeline.id"
- :project-id="mr.targetProjectId"
+ :project-id="mr.sourceProjectId"
:security-reports-docs-path="mr.securityReportsDocsPath"
/>
diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss
index a5e357c8fda..693632c3af1 100644
--- a/app/assets/stylesheets/fontawesome_custom.scss
+++ b/app/assets/stylesheets/fontawesome_custom.scss
@@ -27,10 +27,6 @@
font-size: 2em;
}
-.fa-caret-down::before {
- content: '\f0d7';
-}
-
.fa-exclamation-triangle::before {
content: '\f071';
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 1e051b3bcf0..db41d28575f 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -231,16 +231,6 @@
color: $gray-700;
}
- .fa-caret-down {
- margin-left: 5px;
- }
-
- &.dropdown-toggle {
- .fa-caret-down {
- margin-left: 3px;
- }
- }
-
&.btn-text-field {
width: 100%;
text-align: left;
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 51870ace23b..1caf62067a6 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -201,14 +201,6 @@ ul.related-merge-requests > li {
}
}
}
-
- .create-merge-request-dropdown-toggle {
- .fa-caret-down {
- pointer-events: none;
- color: inherit;
- margin-left: 0;
- }
- }
}
.discussion-reply-holder {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index e5a9e99b2fb..974f74a4368 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -148,15 +148,6 @@
fill: $layout-link-gray;
}
- .fa-caret-down {
- margin-left: 3px;
- line-height: 0;
-
- &.dropdown-btn-icon {
- margin-left: 0;
- }
- }
-
.notifications-icon {
top: 1px;
margin-right: 0;
diff --git a/app/models/ci/build_dependencies.rb b/app/models/ci/build_dependencies.rb
index 7e690dc413c..2c6a9a63bdb 100644
--- a/app/models/ci/build_dependencies.rb
+++ b/app/models/ci/build_dependencies.rb
@@ -2,6 +2,8 @@
module Ci
class BuildDependencies
+ include ::Gitlab::Utils::StrongMemoize
+
attr_reader :processable
def initialize(processable)
@@ -9,7 +11,7 @@ module Ci
end
def all
- (local + cross_project).uniq
+ (local + cross_pipeline + cross_project).uniq
end
# Dependencies local to the given pipeline
@@ -23,6 +25,14 @@ module Ci
deps
end
+ # Dependencies from the same parent-pipeline hierarchy excluding
+ # the current job's pipeline
+ def cross_pipeline
+ strong_memoize(:cross_pipeline) do
+ fetch_dependencies_in_hierarchy
+ end
+ end
+
# Dependencies that are defined by project and ref
def cross_project
[]
@@ -33,7 +43,7 @@ module Ci
end
def valid?
- valid_local? && valid_cross_project?
+ valid_local? && valid_cross_pipeline? && valid_cross_project?
end
private
@@ -44,6 +54,54 @@ module Ci
::Ci::Build
end
+ def fetch_dependencies_in_hierarchy
+ deps_specifications = specified_cross_pipeline_dependencies
+ return [] if deps_specifications.empty?
+
+ deps_specifications = expand_variables_and_validate(deps_specifications)
+ jobs_in_pipeline_hierarchy(deps_specifications)
+ end
+
+ def jobs_in_pipeline_hierarchy(deps_specifications)
+ all_pipeline_ids = []
+ all_job_names = []
+
+ deps_specifications.each do |spec|
+ all_pipeline_ids << spec[:pipeline]
+ all_job_names << spec[:job]
+ end
+
+ model_class.latest.success
+ .in_pipelines(processable.pipeline.same_family_pipeline_ids)
+ .in_pipelines(all_pipeline_ids.uniq)
+ .by_name(all_job_names.uniq)
+ .select do |dependency|
+ # the query may not return exact matches pipeline-job, so we filter
+ # them separately.
+ deps_specifications.find do |spec|
+ spec[:pipeline] == dependency.pipeline_id &&
+ spec[:job] == dependency.name
+ end
+ end
+ end
+
+ def expand_variables_and_validate(specifications)
+ specifications.map do |spec|
+ pipeline = ExpandVariables.expand(spec[:pipeline].to_s, processable_variables).to_i
+ # current pipeline is not allowed because local dependencies
+ # should be used instead.
+ next if pipeline == processable.pipeline_id
+
+ job = ExpandVariables.expand(spec[:job], processable_variables)
+
+ { job: job, pipeline: pipeline }
+ end.compact
+ end
+
+ def valid_cross_pipeline?
+ cross_pipeline.size == specified_cross_pipeline_dependencies.size
+ end
+
def valid_local?
return true if Feature.enabled?(:ci_disable_validates_dependencies)
@@ -78,6 +136,22 @@ module Ci
scope.where(name: processable.options[:dependencies])
end
+
+ def processable_variables
+ -> { processable.simple_variables_without_dependencies }
+ end
+
+ def specified_cross_pipeline_dependencies
+ strong_memoize(:specified_cross_pipeline_dependencies) do
+ next [] unless Feature.enabled?(:ci_cross_pipeline_artifacts_download, processable.project, default_enabled: false)
+
+ specified_cross_dependencies.select { |dep| dep[:pipeline] && dep[:artifacts] }
+ end
+ end
+
+ def specified_cross_dependencies
+ Array(processable.options[:cross_dependencies])
+ end
end
end
diff --git a/app/models/identity.rb b/app/models/identity.rb
index 40d9f856abf..fc97c68b756 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -18,6 +18,9 @@ class Identity < ApplicationRecord
scope :with_extern_uid, ->(provider, extern_uid) do
iwhere(extern_uid: normalize_uid(provider, extern_uid)).with_provider(provider)
end
+ scope :with_any_extern_uid, ->(provider) do
+ where.not(extern_uid: nil).with_provider(provider)
+ end
def ldap?
Gitlab::Auth::OAuth::Provider.ldap_provider?(provider)
diff --git a/app/models/user.rb b/app/models/user.rb
index 083117ef8ec..4acb287b51c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1045,7 +1045,7 @@ class User < ApplicationRecord
end
def require_personal_access_token_creation_for_git_auth?
- return false if allow_password_authentication_for_git? || ldap_user?
+ return false if allow_password_authentication_for_git? || password_based_omniauth_user?
PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end
@@ -1063,7 +1063,7 @@ class User < ApplicationRecord
end
def allow_password_authentication_for_git?
- Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !ldap_user?
+ Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !password_based_omniauth_user?
end
def can_change_username?
@@ -1143,6 +1143,18 @@ class User < ApplicationRecord
namespace.find_fork_of(project)
end
+ def password_based_omniauth_user?
+ ldap_user? || crowd_user?
+ end
+
+ def crowd_user?
+ if identities.loaded?
+ identities.find { |identity| identity.provider == 'crowd' && identity.extern_uid.present? }
+ else
+ identities.with_any_extern_uid('crowd').exists?
+ end
+ end
+
def ldap_user?
if identities.loaded?
identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index 8ad4f59373d..1f90263ba4a 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -17,10 +17,10 @@ module Projects
SUPPORTED_VERSION = '4'
- def execute(token, _integration = nil)
+ def execute(token, integration = nil)
return bad_request unless valid_payload_size?
return unprocessable_entity unless self.class.processable?(params)
- return unauthorized unless valid_alert_manager_token?(token)
+ return unauthorized unless valid_alert_manager_token?(token, integration)
process_prometheus_alerts
@@ -53,9 +53,9 @@ module Projects
params['alerts']
end
- def valid_alert_manager_token?(token)
+ def valid_alert_manager_token?(token, integration)
valid_for_manual?(token) ||
- valid_for_alerts_endpoint?(token) ||
+ valid_for_alerts_endpoint?(token, integration) ||
valid_for_managed?(token)
end
@@ -70,11 +70,10 @@ module Projects
end
end
- def valid_for_alerts_endpoint?(token)
- return false unless project.alerts_service_activated?
+ def valid_for_alerts_endpoint?(token, integration)
+ return false unless integration&.active?
- # Here we are enforcing the existence of the token
- compare_token(token, project.alerts_service.token)
+ compare_token(token, integration.token)
end
def valid_for_managed?(token)
diff --git a/changelogs/unreleased/272983-fix-security-mr-widget-forks.yml b/changelogs/unreleased/272983-fix-security-mr-widget-forks.yml
new file mode 100644
index 00000000000..9d3d4e3a56b
--- /dev/null
+++ b/changelogs/unreleased/272983-fix-security-mr-widget-forks.yml
@@ -0,0 +1,5 @@
+---
+title: Fix getting security report information on merge requests from forks
+merge_request: 49354
+author:
+type: fixed
diff --git a/changelogs/unreleased/feature-git-crowd-auth.yml b/changelogs/unreleased/feature-git-crowd-auth.yml
new file mode 100644
index 00000000000..1ad034bda73
--- /dev/null
+++ b/changelogs/unreleased/feature-git-crowd-auth.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Crowd auth for git-over-https
+merge_request: 46935
+author: Thomas Mendoza @tgmachina
+type: added
diff --git a/changelogs/unreleased/include-actual-limit-in-pipeline-errors.yml b/changelogs/unreleased/include-actual-limit-in-pipeline-errors.yml
new file mode 100644
index 00000000000..1820adc4b40
--- /dev/null
+++ b/changelogs/unreleased/include-actual-limit-in-pipeline-errors.yml
@@ -0,0 +1,5 @@
+---
+title: Include actual limit in pipeline limit errors
+merge_request: 48710
+author:
+type: changed
diff --git a/changelogs/unreleased/sy-process-prometheus-alerts-through-http-integrations.yml b/changelogs/unreleased/sy-process-prometheus-alerts-through-http-integrations.yml
new file mode 100644
index 00000000000..c406398cd0d
--- /dev/null
+++ b/changelogs/unreleased/sy-process-prometheus-alerts-through-http-integrations.yml
@@ -0,0 +1,5 @@
+---
+title: Handle prometheus-formatted alert notifications through HTTP integrations
+merge_request: 49268
+author:
+type: fixed
diff --git a/config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml b/config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml
new file mode 100644
index 00000000000..83fe049db8e
--- /dev/null
+++ b/config/feature_flags/development/ci_cross_pipeline_artifacts_download.yml
@@ -0,0 +1,8 @@
+---
+name: ci_cross_pipeline_artifacts_download
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48342
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/287622
+milestone: '13.7'
+type: development
+group: group::continuous integration
+default_enabled: false
diff --git a/config/feature_flags/development/cd_auto_rollback.yml b/config/feature_flags/development/saas_add_seats_button.yml
index 0878fac7d26..1817481439a 100644
--- a/config/feature_flags/development/cd_auto_rollback.yml
+++ b/config/feature_flags/development/saas_add_seats_button.yml
@@ -1,8 +1,8 @@
---
-name: cd_auto_rollback
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45816
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/35404
-milestone: '13.6'
+name: saas_add_seats_button
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49242
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/291060
+milestone: '13.7'
type: development
-group: group::progressive delivery
+group: group::purchase
default_enabled: false
diff --git a/doc/administration/auth/crowd.md b/doc/administration/auth/crowd.md
index 81614c05b63..440d956fc8f 100644
--- a/doc/administration/auth/crowd.md
+++ b/doc/administration/auth/crowd.md
@@ -7,7 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Atlassian Crowd OmniAuth Provider
-Authenticate to GitLab using the Atlassian Crowd OmniAuth provider.
+Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. Enabling
+this provider also allows Crowd authentication for Git-over-https requests.
## Configure a new Crowd application
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
index 5fcc06c526e..504f90b29d3 100644
--- a/doc/development/database_review.md
+++ b/doc/development/database_review.md
@@ -36,9 +36,22 @@ point out specific queries for review and there are no obviously
complex queries, it is enough to concentrate on reviewing the
migration only.
-It is preferable to review queries in SQL form and generally accepted
-to ask the author to translate any ActiveRecord queries in SQL form
-for review.
+### Required
+
+The following artifacts are required prior to submitting for a ~database review.
+If your merge request description does not include these items, the review will be reassigned back to the author.
+
+If new migrations are introduced, in the MR **you are required to provide**:
+
+- The output of both migrating and rolling back for all migrations
+
+If new queries have been introduced or existing queries have been updated, **you are required to provide**:
+
+- [Query plans](#query-plans) for each raw SQL query included in the merge request along with the link to the query plan following each raw SQL snippet.
+- [Raw SQL](#raw-sql) for all queries (as translated from ActiveRecord queries).
+ - In case of updating an existing query, the raw SQL of both the old and the new version of the query should be provided together with their query plans.
+
+Refer to [Preparation when adding or modifying queries](#preparation-when-adding-or-modifying-queries) for how to provide this information.
### Roles and process
@@ -47,9 +60,11 @@ A Merge Request **author**'s role is to:
- Decide whether a database review is needed.
- If database review is needed, add the ~database label.
- [Prepare the merge request for a database review](#how-to-prepare-the-merge-request-for-a-database-review).
+- Provide the [required](#required) artifacts prior to submitting the MR.
A database **reviewer**'s role is to:
+- Ensure the [required](#required) artifacts are provided and in the proper format. If they are not, reassign the merge request back to the author.
- Perform a first-pass review on the MR and suggest improvements to the author.
- Once satisfied, relabel the MR with ~"database::reviewed", approve it, and
reassign MR to the database **maintainer** suggested by Reviewer
@@ -104,23 +119,43 @@ test its execution using `CREATE INDEX CONCURRENTLY` in the `#database-lab` Slac
#### Preparation when adding or modifying queries
+##### Raw SQL
+
- Write the raw SQL in the MR description. Preferably formatted
nicely with [pgFormatter](https://sqlformat.darold.net) or
[paste.depesz.com](https://paste.depesz.com) and using regular quotes
(e.g. `"projects"."id"`) and avoiding smart quotes (e.g. `“projects”.“id”`).
-- Include the output of `EXPLAIN (ANALYZE, BUFFERS)` of the relevant
- queries in the description. If the output is too long, wrap it in
- `<details>` blocks, paste it in a GitLab Snippet, or provide the
- link to the plan at: [explain.depesz.com](https://explain.depesz.com).
+- In case of queries generated dynamically by using parameters, there should be one raw SQL query for each variation.
+
+ For example, a finder for issues that may take as a parameter an optional filter on projects,
+ should include both the version of the simple query over issues and the one that joins issues
+ and projects and applies the filter.
+
+ There are finders or other methods that can generate a very large amount of permutations.
+ There is no need to exhaustively add all the possible generated queries, just the one with
+ all the parameters included and one for each type of queries generated.
+
+ For example, if joins or a group by clause are optional, the versions without the group by clause
+ and with less joins should be also included, while keeping the appropriate filters for the remaining tables.
+
+- If a query is going to be always used with a limit and an offset, those should always be
+ included with the maximum allowed limit used and a non 0 offset.
+
+##### Query Plans
+
+- The query plan for each raw SQL query included in the merge request along with the link to the query plan following each raw SQL snippet.
+- Provide the link to the plan at: [explain.depesz.com](https://explain.depesz.com). Paste both the plan and the query used in the form.
- When providing query plans, make sure it hits enough data:
- You can use a GitLab production replica to test your queries on a large scale,
through the `#database-lab` Slack channel or through [chatops](understanding_explain_plans.md#chatops).
- Usually, the `gitlab-org` namespace (`namespace_id = 9970`) and the
`gitlab-org/gitlab-foss` (`project_id = 13083`) or the `gitlab-org/gitlab` (`project_id = 278964`)
projects provide enough data to serve as a good example.
-- For query changes, it is best to provide the SQL query along with a
- plan _before_ and _after_ the change. This helps to spot differences
- quickly.
+ - That means that no query plan should return 0 records or less records than the provided limit (if a limit is included). If a query is used in batching, a proper example batch with adequate included results should be identified and provided.
+ - If your queries belong to a new feature in GitLab.com and thus they don't return data in production, it's suggested to analyze the query and to provide the plan from a local environment.
+ - More info on how to find the number of actual returned records in [Understanding EXPLAIN plans](understanding_explain_plans.md)
+- For query changes, it is best to provide both the SQL queries along with the
+ plan _before_ and _after_ the change. This helps spot differences quickly.
- Include data that shows the performance improvement, preferably in
the form of a benchmark.
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index f3824d44d35..95ef0365eb5 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -194,11 +194,13 @@ If the information you need isn't listed above you may wish to check our [troubl
Once Group SSO is configured and enabled, users can access the GitLab.com group through the identity provider's dashboard. If [SCIM](scim_setup.md) is configured, please see the [user access and linking setup section on the SCIM page](scim_setup.md#user-access-and-linking-setup).
-When a user tries to sign in with Group SSO, they need an account that's configured with one of the following:
+When a user tries to sign in with Group SSO, GitLab attempts to find or create a user based on the following:
-- [SCIM](scim_setup.md).
-- [Group-managed accounts](group_managed_accounts.md).
-- A GitLab.com account.
+- Find an existing user with a matching SAML identity. This would mean the user either had their account created by [SCIM](scim_setup.md) or they have previously signed in with the group's SAML IdP.
+- If there is no conflicting user with the same email address, create a new account automatically.
+- If there is a conflicting user with the same email address, redirect the user to the sign-in page to:
+ - Create a new account with another email address.
+ - Sign-in to their existing account to link the SAML identity.
### Linking SAML to your existing GitLab.com account
diff --git a/doc/user/project/import/jira.md b/doc/user/project/import/jira.md
index b97af3c516c..5fb737cf74a 100644
--- a/doc/user/project/import/jira.md
+++ b/doc/user/project/import/jira.md
@@ -33,7 +33,7 @@ Our parser for converting text in Jira issues to GitLab Flavored Markdown is onl
Jira V3 REST API.
There is an [epic](https://gitlab.com/groups/gitlab-org/-/epics/2738) tracking the addition of
-items, such as issue assignees, comments, and much more. These will be included in the future
+items, such as issue assignees, comments, and much more. These are included in the future
iterations of the GitLab Jira importer.
## Prerequisites
@@ -76,7 +76,7 @@ Importing large projects may take several minutes depending on the size of the i
1. Click the **Import from** dropdown and select the Jira project that you wish to import issues from.
In the **Jira-GitLab user mapping template** section, the table shows to which GitLab users your Jira
- users will be mapped.
+ users are mapped.
When the form appears, the dropdown defaults to the user conducting the import.
1. To change any of the mappings, click the dropdown in the **GitLab username** column and
@@ -88,6 +88,6 @@ Importing large projects may take several minutes depending on the size of the i
1. Click **Continue**. You're presented with a confirmation that import has started.
While the import is running in the background, you can navigate away from the import status page
- to the issues page, and you'll see the new issues appearing in the issues list.
+ to the issues page, and you can see the new issues appearing in the issues list.
1. To check the status of your import, go to the Jira import page again.
diff --git a/doc/user/search/advanced_global_search.md b/doc/user/search/advanced_global_search.md
index 3a52cb3671f..1898bdf06a9 100644
--- a/doc/user/search/advanced_global_search.md
+++ b/doc/user/search/advanced_global_search.md
@@ -43,11 +43,7 @@ The Advanced Search can be useful in various scenarios.
### Faster searches
-If you are dealing with huge amount of data and want to keep GitLab's search
-fast, Advanced Search will help you achieve that.
-
-NOTE:
-Between versions 12.10 and 13.4, Advanced Search response times have improved by 80%.
+Advanced Search is based on Elasticsearch, which is a purpose built full text search engine that can be horizontally scaled so that it can provide search results in 1-2 seconds in most cases.
### Promote innersourcing
diff --git a/lib/gitlab/auth/crowd/authentication.rb b/lib/gitlab/auth/crowd/authentication.rb
new file mode 100644
index 00000000000..7f3e980034e
--- /dev/null
+++ b/lib/gitlab/auth/crowd/authentication.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Auth
+ module Crowd
+ class Authentication < Gitlab::Auth::OAuth::Authentication
+ def login(login, password)
+ return unless Gitlab::Auth::OAuth::Provider.enabled?(@provider)
+ return unless login.present? && password.present?
+
+ user_info = user_info_from_authentication(login, password)
+ return unless user_info&.key?(:user)
+
+ Gitlab::Auth::OAuth::User.find_by_uid_and_provider(user_info[:user], provider)
+ end
+
+ private
+
+ def config
+ gitlab_crowd_config = Gitlab::Auth::OAuth::Provider.config_for(@provider)
+ raise "OmniAuth Crowd is not configured." unless gitlab_crowd_config && gitlab_crowd_config[:args]
+
+ OmniAuth::Strategies::Crowd::Configuration.new(
+ gitlab_crowd_config[:args].symbolize_keys)
+ end
+
+ def user_info_from_authentication(login, password)
+ validator = OmniAuth::Strategies::Crowd::CrowdValidator.new(
+ config, login, password, RequestContext.instance.client_ip, nil)
+ validator&.user_info&.symbolize_keys
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/ldap/user.rb b/lib/gitlab/auth/ldap/user.rb
index 1405fb4ab95..814c17b7e44 100644
--- a/lib/gitlab/auth/ldap/user.rb
+++ b/lib/gitlab/auth/ldap/user.rb
@@ -11,16 +11,6 @@ module Gitlab
module Ldap
class User < Gitlab::Auth::OAuth::User
extend ::Gitlab::Utils::Override
- class << self
- # rubocop: disable CodeReuse/ActiveRecord
- def find_by_uid_and_provider(uid, provider)
- identity = ::Identity.with_extern_uid(provider, uid).take
-
- identity && identity.user
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
-
def save
super('LDAP')
end
@@ -30,10 +20,6 @@ module Gitlab
find_by_uid_and_provider || find_by_email || build_new_user
end
- def find_by_uid_and_provider
- self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
- end
-
override :should_save?
def should_save?
gl_user.changed? || gl_user.identities.any?(&:changed?)
diff --git a/lib/gitlab/auth/o_auth/provider.rb b/lib/gitlab/auth/o_auth/provider.rb
index 1eae7af442d..57ff3fcd1f0 100644
--- a/lib/gitlab/auth/o_auth/provider.rb
+++ b/lib/gitlab/auth/o_auth/provider.rb
@@ -18,6 +18,8 @@ module Gitlab
authenticator =
case provider
+ when /crowd/
+ Gitlab::Auth::Crowd::Authentication
when /^ldap/
Gitlab::Auth::Ldap::Authentication
when 'database'
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index 3211d2ffaea..f556a7f40e9 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -9,6 +9,16 @@ module Gitlab
module Auth
module OAuth
class User
+ class << self
+ # rubocop: disable CodeReuse/ActiveRecord
+ def find_by_uid_and_provider(uid, provider)
+ identity = ::Identity.with_extern_uid(provider, uid).take
+
+ identity && identity.user
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+
SignupDisabledError = Class.new(StandardError)
SigninDisabledForProviderError = Class.new(StandardError)
@@ -190,15 +200,12 @@ module Gitlab
@auth_hash = AuthHash.new(auth_hash)
end
- # rubocop: disable CodeReuse/ActiveRecord
def find_by_uid_and_provider
- identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take
- identity&.user
+ self.class.find_by_uid_and_provider(auth_hash.uid, auth_hash.provider)
end
- # rubocop: enable CodeReuse/ActiveRecord
- def build_new_user
- user_params = user_attributes.merge(skip_confirmation: true)
+ def build_new_user(skip_confirmation: true)
+ user_params = user_attributes.merge(skip_confirmation: skip_confirmation)
Users::BuildService.new(nil, user_params).execute(skip_authorization: true)
end
diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb
index 98b080554d4..3c679dce9aa 100644
--- a/lib/gitlab/ci/features.rb
+++ b/lib/gitlab/ci/features.rb
@@ -55,10 +55,6 @@ module Gitlab
::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false)
end
- def self.auto_rollback_available?(project)
- ::Feature.enabled?(:cd_auto_rollback, project) && project&.feature_available?(:auto_rollback)
- end
-
def self.seed_block_run_before_workflow_rules_enabled?(project)
::Feature.enabled?(:ci_seed_block_run_before_workflow_rules, project, default_enabled: true)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ec87de01384..700ba75974c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7336,9 +7336,6 @@ msgstr ""
msgid "ContainerRegistry|%{title} was successfully scheduled for deletion"
msgstr ""
-msgid "ContainerRegistry|%{toggleStatus} - Tags matching the patterns defined below will be scheduled for deletion"
-msgstr ""
-
msgid "ContainerRegistry|Build an image"
msgstr ""
@@ -7351,9 +7348,6 @@ msgstr ""
msgid "ContainerRegistry|Cleanup policy successfully saved."
msgstr ""
-msgid "ContainerRegistry|Cleanup policy:"
-msgstr ""
-
msgid "ContainerRegistry|Cleanup timed out before it could delete all tags"
msgstr ""
@@ -7384,9 +7378,6 @@ msgstr ""
msgid "ContainerRegistry|Docker connection error"
msgstr ""
-msgid "ContainerRegistry|Expiration interval:"
-msgstr ""
-
msgid "ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}"
msgstr ""
@@ -7396,9 +7387,6 @@ msgstr ""
msgid "ContainerRegistry|Expiration policy will run in %{time}"
msgstr ""
-msgid "ContainerRegistry|Expiration schedule:"
-msgstr ""
-
msgid "ContainerRegistry|Filter by name"
msgstr ""
@@ -7441,9 +7429,6 @@ msgstr ""
msgid "ContainerRegistry|Note: Any policy update will result in a change to the scheduled run date and time"
msgstr ""
-msgid "ContainerRegistry|Number of tags to retain:"
-msgstr ""
-
msgid "ContainerRegistry|Published %{timeInfo}"
msgstr ""
@@ -7518,12 +7503,6 @@ msgstr ""
msgid "ContainerRegistry|Tags that match these rules are %{strongStart}removed%{strongEnd}, unless a rule above says to keep them."
msgstr ""
-msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}"
-msgstr ""
-
-msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}expire:%{italicEnd}"
-msgstr ""
-
msgid "ContainerRegistry|Tags with names that match this regex pattern are kept. %{linkStart}More information%{linkEnd}"
msgstr ""
@@ -7563,12 +7542,6 @@ msgstr ""
msgid "ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}"
msgstr ""
-msgid "ContainerRegistry|Wildcards such as %{codeStart}.*-master%{codeEnd} or %{codeStart}release-.*%{codeEnd} are supported"
-msgstr ""
-
-msgid "ContainerRegistry|Wildcards such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
-msgstr ""
-
msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
@@ -9619,15 +9592,27 @@ msgstr ""
msgid "DevopsAdoption|Add new segment"
msgstr ""
+msgid "DevopsAdoption|An error occured while deleting the segment. Please try again."
+msgstr ""
+
msgid "DevopsAdoption|An error occured while saving the segment. Please try again."
msgstr ""
msgid "DevopsAdoption|Approvals"
msgstr ""
+msgid "DevopsAdoption|Are you sure that you would like to delete %{name}?"
+msgstr ""
+
+msgid "DevopsAdoption|Confirm delete segment"
+msgstr ""
+
msgid "DevopsAdoption|Create new segment"
msgstr ""
+msgid "DevopsAdoption|Delete segment"
+msgstr ""
+
msgid "DevopsAdoption|Deploys"
msgstr ""
diff --git a/spec/factories/alert_management/http_integrations.rb b/spec/factories/alert_management/http_integrations.rb
index 2b5864c8587..405ec09251f 100644
--- a/spec/factories/alert_management/http_integrations.rb
+++ b/spec/factories/alert_management/http_integrations.rb
@@ -11,6 +11,10 @@ FactoryBot.define do
active { false }
end
+ trait :active do
+ active { true }
+ end
+
trait :legacy do
endpoint_identifier { 'legacy' }
end
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index cd79f948926..fef7676e795 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -249,7 +249,7 @@ describe('DiffsStoreActions', () => {
{ type: types.SET_LOADING, payload: true },
{ type: types.SET_LOADING, payload: false },
{ type: types.SET_MERGE_REQUEST_DIFFS, payload: diffMetadata.merge_request_diffs },
- { type: types.SET_DIFF_DATA, payload: noFilesData },
+ { type: types.SET_DIFF_METADATA, payload: noFilesData },
],
[],
() => {
diff --git a/spec/frontend/diffs/store/mutations_spec.js b/spec/frontend/diffs/store/mutations_spec.js
index c061c5c2590..13e7cad835d 100644
--- a/spec/frontend/diffs/store/mutations_spec.js
+++ b/spec/frontend/diffs/store/mutations_spec.js
@@ -67,24 +67,24 @@ describe('DiffsStoreMutations', () => {
});
});
- describe('SET_DIFF_DATA', () => {
- it('should not modify the existing state', () => {
+ describe('SET_DIFF_METADATA', () => {
+ it('should overwrite state with the camelCased data that is passed in', () => {
const state = {
- diffFiles: [
- {
- content_sha: diffFileMockData.content_sha,
- file_hash: diffFileMockData.file_hash,
- [INLINE_DIFF_LINES_KEY]: [],
- },
- ],
+ diffFiles: [],
};
const diffMock = {
diff_files: [diffFileMockData],
};
+ const metaMock = {
+ other_key: 'value',
+ };
- mutations[types.SET_DIFF_DATA](state, diffMock);
+ mutations[types.SET_DIFF_METADATA](state, diffMock);
+ expect(state.diffFiles[0]).toEqual(diffFileMockData);
- expect(state.diffFiles[0][INLINE_DIFF_LINES_KEY]).toEqual([]);
+ mutations[types.SET_DIFF_METADATA](state, metaMock);
+ expect(state.diffFiles[0]).toEqual(diffFileMockData);
+ expect(state.otherKey).toEqual('value');
});
});
diff --git a/spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap b/spec/frontend/registry/settings/__snapshots__/utils_spec.js.snap
index 7062773b46b..7062773b46b 100644
--- a/spec/frontend/registry/shared/__snapshots__/utils_spec.js.snap
+++ b/spec/frontend/registry/settings/__snapshots__/utils_spec.js.snap
diff --git a/spec/frontend/registry/settings/components/expiration_input_spec.js b/spec/frontend/registry/settings/components/expiration_input_spec.js
index cb5034d2864..849f85aa265 100644
--- a/spec/frontend/registry/settings/components/expiration_input_spec.js
+++ b/spec/frontend/registry/settings/components/expiration_input_spec.js
@@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlSprintf, GlFormInput, GlLink } from '@gitlab/ui';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import component from '~/registry/settings/components/expiration_input.vue';
-import { NAME_REGEX_LENGTH } from '~/registry/shared/constants';
+import { NAME_REGEX_LENGTH } from '~/registry/settings/constants';
describe('ExpirationInput', () => {
let wrapper;
diff --git a/spec/frontend/registry/settings/components/registry_settings_app_spec.js b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
index 6be24b81ac1..80ef60c0b0b 100644
--- a/spec/frontend/registry/settings/components/registry_settings_app_spec.js
+++ b/spec/frontend/registry/settings/components/registry_settings_app_spec.js
@@ -5,8 +5,8 @@ import createMockApollo from 'jest/helpers/mock_apollo_helper';
import component from '~/registry/settings/components/registry_settings_app.vue';
import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expiration_policy.graphql';
import SettingsForm from '~/registry/settings/components/settings_form.vue';
-import { FETCH_SETTINGS_ERROR_MESSAGE } from '~/registry/shared/constants';
import {
+ FETCH_SETTINGS_ERROR_MESSAGE,
UNAVAILABLE_FEATURE_INTRO_TEXT,
UNAVAILABLE_USER_FEATURE_TEXT,
} from '~/registry/settings/constants';
diff --git a/spec/frontend/registry/settings/components/settings_form_spec.js b/spec/frontend/registry/settings/components/settings_form_spec.js
index 02e57396c42..33be14f4d2e 100644
--- a/spec/frontend/registry/settings/components/settings_form_spec.js
+++ b/spec/frontend/registry/settings/components/settings_form_spec.js
@@ -9,7 +9,7 @@ import expirationPolicyQuery from '~/registry/settings/graphql/queries/get_expir
import {
UPDATE_SETTINGS_ERROR_MESSAGE,
UPDATE_SETTINGS_SUCCESS_MESSAGE,
-} from '~/registry/shared/constants';
+} from '~/registry/settings/constants';
import { GlCard, GlLoadingIcon } from '../../shared/stubs';
import { expirationPolicyPayload, expirationPolicyMutationPayload } from '../mock_data';
diff --git a/spec/frontend/registry/shared/utils_spec.js b/spec/frontend/registry/settings/utils_spec.js
index 1ba832f67ea..f92d51db307 100644
--- a/spec/frontend/registry/shared/utils_spec.js
+++ b/spec/frontend/registry/settings/utils_spec.js
@@ -2,7 +2,7 @@ import {
formOptionsGenerator,
optionLabelGenerator,
olderThanTranslationGenerator,
-} from '~/registry/shared/utils';
+} from '~/registry/settings/utils';
describe('Utils', () => {
describe('optionLabelGenerator', () => {
diff --git a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap b/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
deleted file mode 100644
index 2ceb2655d40..00000000000
--- a/spec/frontend/registry/shared/components/__snapshots__/expiration_policy_fields_spec.js.snap
+++ /dev/null
@@ -1,148 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Expiration Policy Form renders 1`] = `
-<div
- class="gl-line-height-20"
->
- <gl-form-group-stub
- id="expiration-policy-toggle-group"
- label="Cleanup policy:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-toggle"
- >
- <div
- class="gl-display-flex"
- >
- <gl-toggle-stub
- id="expiration-policy-toggle"
- labelposition="top"
- />
-
- <span
- class="gl-mb-3 gl-ml-3 gl-line-height-20"
- >
- <strong>
- Disabled
- </strong>
- - Tags matching the patterns defined below will be scheduled for deletion
- </span>
- </div>
- </gl-form-group-stub>
-
- <gl-form-group-stub
- id="expiration-policy-interval-group"
- label="Expiration interval:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-interval"
- >
- <gl-form-select-stub
- disabled="true"
- id="expiration-policy-interval"
- >
- <option
- value="foo"
- >
-
- Foo
-
- </option>
- <option
- value="bar"
- >
-
- Bar
-
- </option>
- </gl-form-select-stub>
- </gl-form-group-stub>
- <gl-form-group-stub
- id="expiration-policy-schedule-group"
- label="Expiration schedule:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-schedule"
- >
- <gl-form-select-stub
- disabled="true"
- id="expiration-policy-schedule"
- >
- <option
- value="foo"
- >
-
- Foo
-
- </option>
- <option
- value="bar"
- >
-
- Bar
-
- </option>
- </gl-form-select-stub>
- </gl-form-group-stub>
- <gl-form-group-stub
- id="expiration-policy-latest-group"
- label="Number of tags to retain:"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-latest"
- >
- <gl-form-select-stub
- disabled="true"
- id="expiration-policy-latest"
- >
- <option
- value="foo"
- >
-
- Foo
-
- </option>
- <option
- value="bar"
- >
-
- Bar
-
- </option>
- </gl-form-select-stub>
- </gl-form-group-stub>
-
- <gl-form-group-stub
- id="expiration-policy-name-matching-group"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-name-matching"
- >
-
- <gl-form-textarea-stub
- disabled="true"
- id="expiration-policy-name-matching"
- noresize="true"
- placeholder=""
- trim=""
- value=""
- />
- </gl-form-group-stub>
- <gl-form-group-stub
- id="expiration-policy-keep-name-group"
- label-align="right"
- label-cols="3"
- label-for="expiration-policy-keep-name"
- >
-
- <gl-form-textarea-stub
- disabled="true"
- id="expiration-policy-keep-name"
- noresize="true"
- placeholder=""
- trim=""
- value=""
- />
- </gl-form-group-stub>
-</div>
-`;
diff --git a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js b/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
deleted file mode 100644
index bee9bca5369..00000000000
--- a/spec/frontend/registry/shared/components/expiration_policy_fields_spec.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlSprintf } from '@gitlab/ui';
-import component from '~/registry/shared/components/expiration_policy_fields.vue';
-
-import { NAME_REGEX_LENGTH, ENABLED_TEXT, DISABLED_TEXT } from '~/registry/shared/constants';
-import { formOptions } from '../mock_data';
-
-describe('Expiration Policy Form', () => {
- let wrapper;
-
- const FORM_ELEMENTS_ID_PREFIX = '#expiration-policy';
-
- const findFormGroup = name => wrapper.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}-group`);
- const findFormElements = (name, parent = wrapper) =>
- parent.find(`${FORM_ELEMENTS_ID_PREFIX}-${name}`);
-
- const mountComponent = props => {
- wrapper = shallowMount(component, {
- stubs: {
- GlSprintf,
- },
- propsData: {
- formOptions,
- ...props,
- },
- methods: {
- // override idGenerator to avoid having to test with dynamic uid
- idGenerator: value => value,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- it('renders', () => {
- mountComponent();
- expect(wrapper.element).toMatchSnapshot();
- });
-
- describe.each`
- elementName | modelName | value | disabledByToggle
- ${'toggle'} | ${'enabled'} | ${true} | ${'not disabled'}
- ${'interval'} | ${'olderThan'} | ${'foo'} | ${'disabled'}
- ${'schedule'} | ${'cadence'} | ${'foo'} | ${'disabled'}
- ${'latest'} | ${'keepN'} | ${'foo'} | ${'disabled'}
- ${'name-matching'} | ${'nameRegex'} | ${'foo'} | ${'disabled'}
- ${'keep-name'} | ${'nameRegexKeep'} | ${'bar'} | ${'disabled'}
- `(
- `${FORM_ELEMENTS_ID_PREFIX}-$elementName form element`,
- ({ elementName, modelName, value, disabledByToggle }) => {
- it(`${elementName} form group exist in the dom`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- expect(formGroup.exists()).toBe(true);
- });
-
- it(`${elementName} form group has a label-for property`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- expect(formGroup.attributes('label-for')).toBe(`expiration-policy-${elementName}`);
- });
-
- it(`${elementName} form group has a label-cols property`, () => {
- mountComponent({ labelCols: '1' });
- const formGroup = findFormGroup(elementName);
- return wrapper.vm.$nextTick().then(() => {
- expect(formGroup.attributes('label-cols')).toBe('1');
- });
- });
-
- it(`${elementName} form group has a label-align property`, () => {
- mountComponent({ labelAlign: 'foo' });
- const formGroup = findFormGroup(elementName);
- return wrapper.vm.$nextTick().then(() => {
- expect(formGroup.attributes('label-align')).toBe('foo');
- });
- });
-
- it(`${elementName} form group contains an input element`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- expect(findFormElements(elementName, formGroup).exists()).toBe(true);
- });
-
- it(`${elementName} form element change updated ${modelName} with ${value}`, () => {
- mountComponent();
- const formGroup = findFormGroup(elementName);
- const element = findFormElements(elementName, formGroup);
-
- const modelUpdateEvent = element.vm.$options.model
- ? element.vm.$options.model.event
- : 'input';
- element.vm.$emit(modelUpdateEvent, value);
- return wrapper.vm.$nextTick().then(() => {
- expect(wrapper.emitted('input')).toEqual([
- [{ newValue: { [modelName]: value }, modified: modelName }],
- ]);
- });
- });
-
- it(`${elementName} is ${disabledByToggle} by enabled set to false`, () => {
- mountComponent({ settings: { enabled: false } });
- const formGroup = findFormGroup(elementName);
- const expectation = disabledByToggle === 'disabled' ? 'true' : undefined;
- expect(findFormElements(elementName, formGroup).attributes('disabled')).toBe(expectation);
- });
- },
- );
-
- describe('when isLoading is true', () => {
- beforeEach(() => {
- mountComponent({ isLoading: true });
- });
-
- it.each`
- elementName
- ${'toggle'}
- ${'interval'}
- ${'schedule'}
- ${'latest'}
- ${'name-matching'}
- ${'keep-name'}
- `(`${FORM_ELEMENTS_ID_PREFIX}-$elementName is disabled`, ({ elementName }) => {
- expect(findFormElements(elementName).attributes('disabled')).toBe('true');
- });
- });
-
- describe.each`
- modelName | elementName
- ${'nameRegex'} | ${'name-matching'}
- ${'nameRegexKeep'} | ${'keep-name'}
- `('regex textarea validation', ({ modelName, elementName }) => {
- const invalidString = new Array(NAME_REGEX_LENGTH + 2).join(',');
-
- describe('when apiError contains an error message', () => {
- const errorMessage = 'something went wrong';
-
- it('shows the error message on the relevant field', () => {
- mountComponent({ apiErrors: { [modelName]: errorMessage } });
- expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
- });
-
- it('gives precedence to API errors compared to local ones', () => {
- mountComponent({
- apiErrors: { [modelName]: errorMessage },
- value: { [modelName]: invalidString },
- });
- expect(findFormGroup(elementName).attributes('invalid-feedback')).toBe(errorMessage);
- });
- });
-
- describe('when apiErrors is empty', () => {
- it('if the user did not type validation is null', async () => {
- mountComponent({ value: { [modelName]: '' } });
- expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
- expect(wrapper.emitted('validated')).toBeTruthy();
- });
-
- it(`if the user typed and is less than ${NAME_REGEX_LENGTH} state is true`, () => {
- mountComponent({ value: { [modelName]: 'foo' } });
-
- const formGroup = findFormGroup(elementName);
- const formElement = findFormElements(elementName, formGroup);
- expect(formGroup.attributes('state')).toBeTruthy();
- expect(formElement.attributes('state')).toBeTruthy();
- });
-
- describe(`when name regex is longer than ${NAME_REGEX_LENGTH}`, () => {
- beforeEach(() => {
- mountComponent({ value: { [modelName]: invalidString } });
- });
-
- it('textAreaValidation state is false', () => {
- expect(findFormGroup(elementName).attributes('state')).toBeUndefined();
- // we are forced to check the model attribute because falsy attrs are all casted to undefined in attrs
- // while in this case false shows an error and null instead shows nothing.
- expect(wrapper.vm.textAreaValidation[modelName].state).toBe(false);
- });
-
- it('emit the @invalidated event', () => {
- expect(wrapper.emitted('invalidated')).toBeTruthy();
- });
- });
- });
- });
-
- describe('help text', () => {
- it('toggleDescriptionText show disabled when settings.enabled is false', () => {
- mountComponent();
- const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain(DISABLED_TEXT);
- });
-
- it('toggleDescriptionText show enabled when settings.enabled is true', () => {
- mountComponent({ value: { enabled: true } });
- const toggleHelpText = findFormGroup('toggle').find('span');
- expect(toggleHelpText.html()).toContain(ENABLED_TEXT);
- });
- });
-});
diff --git a/spec/frontend/registry/shared/mock_data.js b/spec/frontend/registry/shared/mock_data.js
deleted file mode 100644
index 411363c2c95..00000000000
--- a/spec/frontend/registry/shared/mock_data.js
+++ /dev/null
@@ -1,12 +0,0 @@
-export const options = [{ key: 'foo', label: 'Foo' }, { key: 'bar', label: 'Bar', default: true }];
-export const stringifiedOptions = JSON.stringify(options);
-export const stringifiedFormOptions = {
- cadenceOptions: stringifiedOptions,
- keepNOptions: stringifiedOptions,
- olderThanOptions: stringifiedOptions,
-};
-export const formOptions = {
- cadence: options,
- keepN: options,
- olderThan: options,
-};
diff --git a/spec/lib/gitlab/auth/crowd/authentication_spec.rb b/spec/lib/gitlab/auth/crowd/authentication_spec.rb
new file mode 100644
index 00000000000..71eb8036fdd
--- /dev/null
+++ b/spec/lib/gitlab/auth/crowd/authentication_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Auth::Crowd::Authentication do
+ let(:provider) { 'crowd' }
+ let(:login) { generate(:username) }
+ let(:password) { 'password' }
+ let(:crowd_auth) { described_class.new(provider) }
+ let(:user_info) { { user: login } }
+
+ describe 'login' do
+ before do
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).with(provider).and_return(true)
+ allow(crowd_auth).to receive(:user_info_from_authentication).and_return(user_info)
+ end
+
+ it "finds the user if authentication is successful" do
+ create(:omniauth_user, extern_uid: login, username: login, provider: provider)
+
+ expect(crowd_auth.login(login, password)).to be_truthy
+ end
+
+ it "is false if the user does not exist" do
+ expect(crowd_auth.login(login, password)).to be_falsey
+ end
+
+ it "is false if the authentication fails" do
+ allow(crowd_auth).to receive(:user_info_from_authentication).and_return(nil)
+
+ expect(crowd_auth.login(login, password)).to be_falsey
+ end
+
+ it "fails when crowd is disabled" do
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).with('crowd').and_return(false)
+
+ expect(crowd_auth.login(login, password)).to be_falsey
+ end
+
+ it "fails if no login is supplied" do
+ expect(crowd_auth.login('', password)).to be_falsey
+ end
+
+ it "fails if no password is supplied" do
+ expect(crowd_auth.login(login, '')).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index ccaed94b5c8..e910ac09448 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -49,23 +49,6 @@ RSpec.describe Gitlab::Auth::Ldap::User do
end
end
- describe '.find_by_uid_and_provider' do
- let(:dn) { 'CN=John Åström, CN=Users, DC=Example, DC=com' }
-
- it 'retrieves the correct user' do
- special_info = {
- name: 'John Åström',
- email: 'john@example.com',
- nickname: 'jastrom'
- }
- special_hash = OmniAuth::AuthHash.new(uid: dn, provider: 'ldapmain', info: special_info)
- special_chars_user = described_class.new(special_hash)
- user = special_chars_user.save
-
- expect(described_class.find_by_uid_and_provider(dn, 'ldapmain')).to eq user
- end
- end
-
describe 'find or create' do
it "finds the user if already existing" do
create(:omniauth_user, extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain')
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index 243d0a4cb45..6c6cee9c273 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -25,6 +25,23 @@ RSpec.describe Gitlab::Auth::OAuth::User do
let(:ldap_user) { Gitlab::Auth::Ldap::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
+ describe '.find_by_uid_and_provider' do
+ let(:dn) { 'CN=John Åström, CN=Users, DC=Example, DC=com' }
+
+ it 'retrieves the correct user' do
+ special_info = {
+ name: 'John Åström',
+ email: 'john@example.com',
+ nickname: 'jastrom'
+ }
+ special_hash = OmniAuth::AuthHash.new(uid: dn, provider: 'ldapmain', info: special_info)
+ special_chars_user = described_class.new(special_hash)
+ user = special_chars_user.save
+
+ expect(described_class.find_by_uid_and_provider(dn, 'ldapmain')).to eq user
+ end
+ end
+
describe '#persisted?' do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
diff --git a/spec/models/ci/build_dependencies_spec.rb b/spec/models/ci/build_dependencies_spec.rb
index 623320370b1..c5f56dbe5bc 100644
--- a/spec/models/ci/build_dependencies_spec.rb
+++ b/spec/models/ci/build_dependencies_spec.rb
@@ -146,6 +146,204 @@ RSpec.describe Ci::BuildDependencies do
end
end
+ describe '#cross_pipeline' do
+ let!(:job) do
+ create(:ci_build,
+ pipeline: pipeline,
+ name: 'build_with_pipeline_dependency',
+ options: { cross_dependencies: dependencies })
+ end
+
+ subject { described_class.new(job) }
+
+ let(:cross_pipeline_deps) { subject.cross_pipeline }
+
+ context 'when dependency specifications are valid' do
+ context 'when pipeline exists in the hierarchy' do
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when job exists' do
+ let(:dependencies) do
+ [{ pipeline: parent_pipeline.id.to_s, job: upstream_job.name, artifacts: true }]
+ end
+
+ let!(:upstream_job) { create(:ci_build, :success, pipeline: parent_pipeline) }
+
+ it { expect(cross_pipeline_deps).to contain_exactly(upstream_job) }
+ it { is_expected.to be_valid }
+
+ context 'when pipeline and job are specified via variables' do
+ let(:dependencies) do
+ [{ pipeline: '$parent_pipeline_ID', job: '$UPSTREAM_JOB', artifacts: true }]
+ end
+
+ before do
+ job.yaml_variables.push(key: 'parent_pipeline_ID', value: parent_pipeline.id.to_s, public: true)
+ job.yaml_variables.push(key: 'UPSTREAM_JOB', value: upstream_job.name, public: true)
+ job.save!
+ end
+
+ it { expect(cross_pipeline_deps).to contain_exactly(upstream_job) }
+ it { is_expected.to be_valid }
+ end
+
+ context 'when feature flag `ci_cross_pipeline_artifacts_download` is disabled' do
+ before do
+ stub_feature_flags(ci_cross_pipeline_artifacts_download: false)
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'when same job names exist in other pipelines in the hierarchy' do
+ let(:cross_pipeline_limit) do
+ ::Gitlab::Ci::Config::Entry::Needs::NEEDS_CROSS_PIPELINE_DEPENDENCIES_LIMIT
+ end
+
+ let(:sibling_pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+
+ before do
+ cross_pipeline_limit.times do |index|
+ create(:ci_build, :success,
+ pipeline: parent_pipeline, name: "dependency-#{index}",
+ stage_idx: 1, stage: 'build', user: user
+ )
+
+ create(:ci_build, :success,
+ pipeline: sibling_pipeline, name: "dependency-#{index}",
+ stage_idx: 1, stage: 'build', user: user
+ )
+ end
+ end
+
+ let(:dependencies) do
+ [
+ { pipeline: parent_pipeline.id.to_s, job: 'dependency-0', artifacts: true },
+ { pipeline: parent_pipeline.id.to_s, job: 'dependency-1', artifacts: true },
+ { pipeline: parent_pipeline.id.to_s, job: 'dependency-2', artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: 'dependency-3', artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: 'dependency-4', artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: 'dependency-5', artifacts: true }
+ ]
+ end
+
+ it 'returns a limited number of dependencies with the right match' do
+ expect(job.options[:cross_dependencies].size).to eq(cross_pipeline_limit.next)
+ expect(cross_pipeline_deps.size).to eq(cross_pipeline_limit)
+ expect(cross_pipeline_deps.map { |dep| [dep.pipeline_id, dep.name] }).to contain_exactly(
+ [parent_pipeline.id, 'dependency-0'],
+ [parent_pipeline.id, 'dependency-1'],
+ [parent_pipeline.id, 'dependency-2'],
+ [sibling_pipeline.id, 'dependency-3'],
+ [sibling_pipeline.id, 'dependency-4'])
+ end
+ end
+
+ context 'when job does not exist' do
+ let(:dependencies) do
+ [{ pipeline: parent_pipeline.id.to_s, job: 'non-existent', artifacts: true }]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ context 'when pipeline does not exist' do
+ let(:dependencies) do
+ [{ pipeline: '123', job: 'non-existent', artifacts: true }]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when jobs exist in different pipelines in the hierarchy' do
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:parent_job) { create(:ci_build, :success, name: 'parent_job', pipeline: parent_pipeline) }
+
+ let!(:sibling_pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:sibling_job) { create(:ci_build, :success, name: 'sibling_job', pipeline: sibling_pipeline) }
+
+ context 'when pipeline and jobs dependencies are mismatched' do
+ let(:dependencies) do
+ [
+ { pipeline: parent_pipeline.id.to_s, job: sibling_job.name, artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: parent_job.name, artifacts: true }
+ ]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.not_to be_valid }
+
+ context 'when dependencies contain a valid pair' do
+ let(:dependencies) do
+ [
+ { pipeline: parent_pipeline.id.to_s, job: sibling_job.name, artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: parent_job.name, artifacts: true },
+ { pipeline: sibling_pipeline.id.to_s, job: sibling_job.name, artifacts: true }
+ ]
+ end
+
+ it 'filters out the invalid ones' do
+ expect(cross_pipeline_deps).to contain_exactly(sibling_job)
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+ end
+
+ context 'when job and pipeline exist outside the hierarchy' do
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:another_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:dependency) { create(:ci_build, :success, pipeline: another_pipeline) }
+
+ let(:dependencies) do
+ [{ pipeline: another_pipeline.id.to_s, job: dependency.name, artifacts: true }]
+ end
+
+ it 'ignores jobs outside the pipeline hierarchy' do
+ expect(cross_pipeline_deps).to be_empty
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when current pipeline is specified' do
+ let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let!(:dependency) { create(:ci_build, :success, pipeline: pipeline) }
+
+ let(:dependencies) do
+ [{ pipeline: pipeline.id.to_s, job: dependency.name, artifacts: true }]
+ end
+
+ it 'ignores jobs from the current pipeline as simple needs should be used instead' do
+ expect(cross_pipeline_deps).to be_empty
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ context 'when artifacts:false' do
+ let!(:pipeline) { create(:ci_pipeline, child_of: parent_pipeline) }
+ let!(:parent_pipeline) { create(:ci_pipeline, project: project) }
+ let!(:parent_job) { create(:ci_build, :success, name: 'parent_job', pipeline: parent_pipeline) }
+
+ let(:dependencies) do
+ [{ pipeline: parent_pipeline.id.to_s, job: parent_job.name, artifacts: false }]
+ end
+
+ it { expect(cross_pipeline_deps).to be_empty }
+ it { is_expected.to be_valid } # we simply ignore it
+ end
+ end
+
describe '#all' do
let!(:job) do
create(:ci_build, pipeline: pipeline, name: 'deploy', stage_idx: 3, stage: 'deploy')
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index 696d33b7beb..c0efb2dff56 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -81,6 +81,36 @@ RSpec.describe Identity do
end
end
+ describe '.with_any_extern_uid' do
+ context 'provider with extern uid' do
+ let!(:test_entity) { create(:identity, provider: 'test_provider', extern_uid: 'test_uid') }
+
+ it 'finds any extern uids associated with a provider' do
+ identity = described_class.with_any_extern_uid('test_provider').first
+
+ expect(identity).to be
+ end
+ end
+
+ context 'provider with nil extern uid' do
+ let!(:nil_entity) { create(:identity, provider: 'nil_entity_provider', extern_uid: nil) }
+
+ it 'has no results when there are no extern uids' do
+ identity = described_class.with_any_extern_uid('nil_entity_provider').first
+
+ expect(identity).to be_nil
+ end
+ end
+
+ context 'no provider' do
+ it 'has no results when there is no associated provider' do
+ identity = described_class.with_any_extern_uid('nonexistent_provider').first
+
+ expect(identity).to be_nil
+ end
+ end
+ end
+
context 'callbacks' do
context 'before_save' do
describe 'normalizes extern uid' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index b6a72a09a97..1b19eb49374 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2535,6 +2535,28 @@ RSpec.describe User do
end
end
+ context 'crowd synchronized user' do
+ describe '#crowd_user?' do
+ it 'is true if provider is crowd' do
+ user = create(:omniauth_user, provider: 'crowd')
+
+ expect(user.crowd_user?).to be_truthy
+ end
+
+ it 'is false for other providers' do
+ user = create(:omniauth_user, provider: 'other-provider')
+
+ expect(user.crowd_user?).to be_falsey
+ end
+
+ it 'is false if no extern_uid is provided' do
+ user = create(:omniauth_user, extern_uid: nil)
+
+ expect(user.crowd_user?).to be_falsey
+ end
+ end
+ end
+
describe '#requires_ldap_check?' do
let(:user) { described_class.new }
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index 0e5ac7c69e3..c8f7359ac38 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -138,10 +138,10 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end
end
- context 'with generic alerts integration' do
+ context 'with HTTP integration' do
using RSpec::Parameterized::TableSyntax
- where(:alerts_service, :token, :result) do
+ where(:active, :token, :result) do
:active | :valid | :success
:active | :invalid | :failure
:active | nil | :failure
@@ -150,15 +150,12 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end
with_them do
- let(:valid) { project.alerts_service.token }
+ let(:valid) { integration.token }
let(:invalid) { 'invalid token' }
let(:token_input) { public_send(token) if token }
+ let(:integration) { create(:alert_management_http_integration, active, project: project) if active }
- before do
- if alerts_service
- create(:alerts_service, alerts_service, project: project)
- end
- end
+ let(:subject) { service.execute(token_input, integration) }
case result = params[:result]
when :success