summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-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
14 files changed, 315 insertions, 153 deletions
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