diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-30 18:12:24 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-30 18:12:24 +0300 |
commit | eaec42f9e37fe51f9c53fa7079639ec9f4c40efc (patch) | |
tree | 2abbab9659bc8e043b2dbb9dcf797a5aab717767 /app/assets | |
parent | 4e8c8922da341914b9fd5570ec9ce7a29ffdfebd (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
7 files changed, 294 insertions, 158 deletions
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue index a490111e13b..8467ea12be1 100644 --- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue +++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue @@ -15,6 +15,8 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import { n__, s__, __ } from '~/locale'; import getProjects from '../graphql/projects.query.graphql'; +const sortByProjectName = (projects = []) => projects.sort((a, b) => a.name.localeCompare(b.name)); + export default { name: 'ProjectsDropdownFilter', components: { @@ -88,6 +90,9 @@ export default { selectedProjectIds() { return this.selectedProjects.map((p) => p.id); }, + hasSelectedProjects() { + return Boolean(this.selectedProjects.length); + }, availableProjects() { return filterBySearchTerm(this.projects, this.searchTerm); }, @@ -95,6 +100,14 @@ export default { const { loading, availableProjects } = this; return !loading && !availableProjects.length; }, + selectedItems() { + return sortByProjectName( + this.availableProjects.filter(({ id }) => this.selectedProjectIds.includes(id)), + ); + }, + unselectedItems() { + return this.availableProjects.filter(({ id }) => !this.selectedProjectIds.includes(id)); + }, }, watch: { searchTerm() { @@ -105,44 +118,53 @@ export default { this.search(); }, methods: { + handleUpdatedSelectedProjects() { + this.$emit('selected', this.selectedProjects); + }, search: debounce(function debouncedSearch() { this.fetchData(); }, DEFAULT_DEBOUNCE_AND_THROTTLE_MS), - getSelectedProjects(selectedProject, isMarking) { - return isMarking + getSelectedProjects(selectedProject, isSelected) { + return isSelected ? this.selectedProjects.concat([selectedProject]) : this.selectedProjects.filter((project) => project.id !== selectedProject.id); }, singleSelectedProject(selectedObj, isMarking) { return isMarking ? [selectedObj] : []; }, - setSelectedProjects(selectedObj, isMarking) { + setSelectedProjects(project) { this.selectedProjects = this.multiSelect - ? this.getSelectedProjects(selectedObj, isMarking) - : this.singleSelectedProject(selectedObj, isMarking); + ? this.getSelectedProjects(project, !this.isProjectSelected(project)) + : this.singleSelectedProject(project, !this.isProjectSelected(project)); }, - onClick({ project, isSelected }) { - this.setSelectedProjects(project, !isSelected); - this.$emit('selected', this.selectedProjects); + onClick(project) { + this.setSelectedProjects(project); + this.handleUpdatedSelectedProjects(); }, - onMultiSelectClick({ project, isSelected }) { - this.setSelectedProjects(project, !isSelected); + onMultiSelectClick(project) { + this.setSelectedProjects(project); this.isDirty = true; }, - onSelected(ev) { + onSelected(project) { if (this.multiSelect) { - this.onMultiSelectClick(ev); + this.onMultiSelectClick(project); } else { - this.onClick(ev); + this.onClick(project); } }, onHide() { if (this.multiSelect && this.isDirty) { - this.$emit('selected', this.selectedProjects); + this.handleUpdatedSelectedProjects(); } this.searchTerm = ''; this.isDirty = false; }, + onClearAll() { + if (this.hasSelectedProjects) { + this.isDirty = true; + } + this.selectedProjects = []; + }, fetchData() { this.loading = true; @@ -168,8 +190,8 @@ export default { this.projects = nodes; }); }, - isProjectSelected(id) { - return this.selectedProjects ? this.selectedProjectIds.includes(id) : false; + isProjectSelected(project) { + return this.selectedProjectIds.includes(project.id); }, getEntityId(project) { return getIdFromGraphQLId(project.id); @@ -182,6 +204,10 @@ export default { ref="projectsDropdown" class="dropdown dropdown-projects" toggle-class="gl-shadow-none" + :show-clear-all="hasSelectedProjects" + show-highlighted-items-title + highlighted-items-title-class="gl-p-3" + @clear-all.stop="onClearAll" @hide="onHide" > <template #button-content> @@ -204,14 +230,37 @@ export default { <gl-dropdown-section-header>{{ __('Projects') }}</gl-dropdown-section-header> <gl-search-box-by-type v-model.trim="searchTerm" /> </template> + <template #highlighted-items> + <gl-dropdown-item + v-for="project in selectedItems" + :key="project.id" + is-check-item + :is-checked="isProjectSelected(project)" + @click.native.capture.stop="onSelected(project)" + > + <div class="gl-display-flex"> + <gl-avatar + class="gl-mr-2 gl-vertical-align-middle" + :alt="project.name" + :size="16" + :entity-id="getEntityId(project)" + :entity-name="project.name" + :src="project.avatarUrl" + shape="rect" + /> + <div> + <div data-testid="project-name">{{ project.name }}</div> + <div class="gl-text-gray-500" data-testid="project-full-path"> + {{ project.fullPath }} + </div> + </div> + </div> + </gl-dropdown-item> + </template> <gl-dropdown-item - v-for="project in availableProjects" + v-for="project in unselectedItems" :key="project.id" - :is-check-item="true" - :is-checked="isProjectSelected(project.id)" - @click.native.capture.stop=" - onSelected({ project, isSelected: isProjectSelected(project.id) }) - " + @click.native.capture.stop="onSelected(project)" > <div class="gl-display-flex"> <gl-avatar diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index b6473322b1f..0052bc00406 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -852,10 +852,6 @@ "description": "Retry if there is a runner system failure (for example, job setup failed)." }, { - "const": "missing_dependency_failure", - "description": "Retry if a dependency is missing." - }, - { "const": "runner_unsupported", "description": "Retry if the runner is unsupported." }, diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue index e3c5aa53129..fbf0ba744bd 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue @@ -1,9 +1,12 @@ <script> import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui'; -import { helpPagePath } from '~/helpers/help_page_helper'; import { __ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; +import { + DEPENDENCY_PROXY_SETTINGS_DESCRIPTION, + DEPENDENCY_PROXY_DOCS_PATH, +} from '~/packages_and_registries/settings/group/constants'; import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql'; @@ -19,9 +22,6 @@ export default { }, inject: ['groupPath', 'dependencyProxyAvailable'], i18n: { - subTitle: __( - 'Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies.', - ), proxyNotAvailableText: __('Dependency proxy feature is limited to public groups for now.'), proxyImagePrefix: __('Dependency proxy image prefix'), copyImagePrefixText: __('Copy prefix'), @@ -47,8 +47,8 @@ export default { infoMessages() { return [ { - text: this.$options.i18n.subTitle, - link: helpPagePath('user/packages/dependency_proxy/index'), + text: DEPENDENCY_PROXY_SETTINGS_DESCRIPTION, + link: DEPENDENCY_PROXY_DOCS_PATH, }, ]; }, diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue index ec3be43196c..954a2aedf6b 100644 --- a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue +++ b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue @@ -1,108 +1,54 @@ <script> -import { GlSprintf, GlLink, GlAlert } from '@gitlab/ui'; -import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue'; -import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue'; -import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue'; +import { GlAlert } from '@gitlab/ui'; import { - PACKAGE_SETTINGS_HEADER, - PACKAGE_SETTINGS_DESCRIPTION, - PACKAGES_DOCS_PATH, ERROR_UPDATING_SETTINGS, SUCCESS_UPDATING_SETTINGS, } from '~/packages_and_registries/settings/group/constants'; -import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql'; +import PackagesSettings from '~/packages_and_registries/settings/group/components/packages_settings.vue'; + import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql'; -import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update'; -import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses'; -import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; export default { name: 'GroupSettingsApp', - i18n: { - PACKAGE_SETTINGS_HEADER, - PACKAGE_SETTINGS_DESCRIPTION, - }, - links: { - PACKAGES_DOCS_PATH, - }, components: { GlAlert, - GlSprintf, - GlLink, - SettingsBlock, - MavenSettings, - GenericSettings, - DuplicatesSettings, + PackagesSettings, }, - inject: ['defaultExpanded', 'groupPath'], + inject: ['groupPath'], apollo: { - packageSettings: { + group: { query: getGroupPackagesSettingsQuery, variables() { return { fullPath: this.groupPath, }; }, - update(data) { - return data.group?.packageSettings; - }, }, }, data() { return { - packageSettings: {}, - errors: {}, + group: {}, alertMessage: null, }; }, computed: { + packageSettings() { + return this.group?.packageSettings || {}; + }, isLoading() { - return this.$apollo.queries.packageSettings.loading; + return this.$apollo.queries.group.loading; }, }, methods: { dismissAlert() { this.alertMessage = null; }, - updateSettings(payload) { - this.errors = {}; - return this.$apollo - .mutate({ - mutation: updateNamespacePackageSettings, - variables: { - input: { - namespacePath: this.groupPath, - ...payload, - }, - }, - update: updateGroupPackageSettings(this.groupPath), - optimisticResponse: updateGroupPackagesSettingsOptimisticResponse({ - ...this.packageSettings, - ...payload, - }), - }) - .then(({ data }) => { - if (data.updateNamespacePackageSettings?.errors?.length > 0) { - this.alertMessage = ERROR_UPDATING_SETTINGS; - } else { - this.dismissAlert(); - this.$toast.show(SUCCESS_UPDATING_SETTINGS); - } - }) - .catch((e) => { - if (e.graphQLErrors) { - e.graphQLErrors.forEach((error) => { - const [ - { - path: [key], - message, - }, - ] = error.extensions.problems; - this.errors = { ...this.errors, [key]: message }; - }); - } - this.alertMessage = ERROR_UPDATING_SETTINGS; - }); + handleSuccess() { + this.$toast.show(SUCCESS_UPDATING_SETTINGS); + this.dismissAlert(); + }, + handleError() { + this.alertMessage = ERROR_UPDATING_SETTINGS; }, }, }; @@ -114,50 +60,11 @@ export default { {{ alertMessage }} </gl-alert> - <settings-block - :default-expanded="defaultExpanded" - data-qa-selector="package_registry_settings_content" - > - <template #title> {{ $options.i18n.PACKAGE_SETTINGS_HEADER }}</template> - <template #description> - <span data-testid="description"> - <gl-sprintf :message="$options.i18n.PACKAGE_SETTINGS_DESCRIPTION"> - <template #link="{ content }"> - <gl-link :href="$options.links.PACKAGES_DOCS_PATH" target="_blank">{{ - content - }}</gl-link> - </template> - </gl-sprintf> - </span> - </template> - <template #default> - <maven-settings data-testid="maven-settings"> - <template #default="{ modelNames }"> - <duplicates-settings - :duplicates-allowed="packageSettings.mavenDuplicatesAllowed" - :duplicate-exception-regex="packageSettings.mavenDuplicateExceptionRegex" - :duplicate-exception-regex-error="errors.mavenDuplicateExceptionRegex" - :model-names="modelNames" - :loading="isLoading" - toggle-qa-selector="allow_duplicates_toggle" - label-qa-selector="allow_duplicates_label" - @update="updateSettings" - /> - </template> - </maven-settings> - <generic-settings class="gl-mt-6" data-testid="generic-settings"> - <template #default="{ modelNames }"> - <duplicates-settings - :duplicates-allowed="packageSettings.genericDuplicatesAllowed" - :duplicate-exception-regex="packageSettings.genericDuplicateExceptionRegex" - :duplicate-exception-regex-error="errors.genericDuplicateExceptionRegex" - :model-names="modelNames" - :loading="isLoading" - @update="updateSettings" - /> - </template> - </generic-settings> - </template> - </settings-block> + <packages-settings + :package-settings="packageSettings" + :is-loading="isLoading" + @success="handleSuccess" + @error="handleError" + /> </div> </template> diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue new file mode 100644 index 00000000000..b7e88945dbd --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue @@ -0,0 +1,139 @@ +<script> +import { GlSprintf, GlLink } from '@gitlab/ui'; +import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue'; +import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue'; +import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue'; +import { + PACKAGE_SETTINGS_HEADER, + PACKAGE_SETTINGS_DESCRIPTION, + PACKAGES_DOCS_PATH, +} from '~/packages_and_registries/settings/group/constants'; +import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql'; +import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update'; +import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses'; +import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue'; + +export default { + name: 'PackageSettings', + i18n: { + PACKAGE_SETTINGS_HEADER, + PACKAGE_SETTINGS_DESCRIPTION, + }, + links: { + PACKAGES_DOCS_PATH, + }, + components: { + GlSprintf, + GlLink, + SettingsBlock, + MavenSettings, + GenericSettings, + DuplicatesSettings, + }, + inject: ['defaultExpanded', 'groupPath'], + props: { + packageSettings: { + type: Object, + required: true, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + errors: {}, + }; + }, + methods: { + async updateSettings(payload) { + this.errors = {}; + try { + const { data } = await this.$apollo.mutate({ + mutation: updateNamespacePackageSettings, + variables: { + input: { + namespacePath: this.groupPath, + ...payload, + }, + }, + update: updateGroupPackageSettings(this.groupPath), + optimisticResponse: updateGroupPackagesSettingsOptimisticResponse({ + ...this.packageSettings, + ...payload, + }), + }); + + if (data.updateNamespacePackageSettings?.errors?.length > 0) { + throw new Error(); + } else { + this.$emit('success'); + } + } catch (e) { + if (e.graphQLErrors) { + e.graphQLErrors.forEach((error) => { + const [ + { + path: [key], + message, + }, + ] = error.extensions.problems; + this.errors = { ...this.errors, [key]: message }; + }); + } + this.$emit('error'); + } + }, + }, +}; +</script> + +<template> + <settings-block + :default-expanded="defaultExpanded" + data-qa-selector="package_registry_settings_content" + > + <template #title> {{ $options.i18n.PACKAGE_SETTINGS_HEADER }}</template> + <template #description> + <span data-testid="description"> + <gl-sprintf :message="$options.i18n.PACKAGE_SETTINGS_DESCRIPTION"> + <template #link="{ content }"> + <gl-link :href="$options.links.PACKAGES_DOCS_PATH" target="_blank">{{ + content + }}</gl-link> + </template> + </gl-sprintf> + </span> + </template> + <template #default> + <maven-settings data-testid="maven-settings"> + <template #default="{ modelNames }"> + <duplicates-settings + :duplicates-allowed="packageSettings.mavenDuplicatesAllowed" + :duplicate-exception-regex="packageSettings.mavenDuplicateExceptionRegex" + :duplicate-exception-regex-error="errors.mavenDuplicateExceptionRegex" + :model-names="modelNames" + :loading="isLoading" + toggle-qa-selector="allow_duplicates_toggle" + label-qa-selector="allow_duplicates_label" + @update="updateSettings" + /> + </template> + </maven-settings> + <generic-settings class="gl-mt-6" data-testid="generic-settings"> + <template #default="{ modelNames }"> + <duplicates-settings + :duplicates-allowed="packageSettings.genericDuplicatesAllowed" + :duplicate-exception-regex="packageSettings.genericDuplicateExceptionRegex" + :duplicate-exception-regex-error="errors.genericDuplicateExceptionRegex" + :model-names="modelNames" + :loading="isLoading" + @update="updateSettings" + /> + </template> + </generic-settings> + </template> + </settings-block> +</template> diff --git a/app/assets/javascripts/packages_and_registries/settings/group/constants.js b/app/assets/javascripts/packages_and_registries/settings/group/constants.js index d29489a0b33..2824d5e2776 100644 --- a/app/assets/javascripts/packages_and_registries/settings/group/constants.js +++ b/app/assets/javascripts/packages_and_registries/settings/group/constants.js @@ -23,8 +23,15 @@ export const ERROR_UPDATING_SETTINGS = s__( 'PackageRegistry|An error occurred while saving the settings', ); +export const DEPENDENCY_PROXY_HEADER = s__('DependencyProxy|Dependency Proxy'); +export const DEPENDENCY_PROXY_SETTINGS_DESCRIPTION = s__( + 'DependencyProxy|Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies.', +); + // Parameters export const PACKAGES_DOCS_PATH = helpPagePath('user/packages'); export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed'; export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex'; + +export const DEPENDENCY_PROXY_DOCS_PATH = helpPagePath('user/packages/dependency_proxy/index'); diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index ebd20583a1c..b350db0c838 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,5 +1,7 @@ import $ from 'jquery'; +import { debounce } from 'lodash'; import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates'; +import axios from '../lib/utils/axios_utils'; import { convertToTitleCase, humanize, @@ -9,6 +11,23 @@ import { let hasUserDefinedProjectPath = false; let hasUserDefinedProjectName = false; +const invalidInputClass = 'gl-field-error-outline'; + +const validateImportCredentials = (url, user, password) => { + const endpoint = `${gon.relative_url_root}/import/url/validate`; + return axios + .post(endpoint, { + url, + user, + password, + }) + .then(({ data }) => data) + .catch(() => ({ + // intentionally reporting success in case of validation error + // we do not want to block users from trying import in case of validation exception + success: true, + })); +}; const onProjectNameChange = ($projectNameInput, $projectPathInput) => { const slug = slugify(convertUnicodeToAscii($projectNameInput.val())); @@ -85,7 +104,10 @@ const bindHowToImport = () => { const bindEvents = () => { const $newProjectForm = $('#new_project'); const $projectImportUrl = $('#project_import_url'); - const $projectImportUrlWarning = $('.js-import-url-warning'); + const $projectImportUrlUser = $('#project_import_url_user'); + const $projectImportUrlPassword = $('#project_import_url_password'); + const $projectImportUrlError = $('.js-import-url-error'); + const $projectImportForm = $('.project-import form'); const $projectPath = $('.tab-pane.active #project_path'); const $useTemplateBtn = $('.template-button > input'); const $projectFieldsForm = $('.project-fields-form'); @@ -139,12 +161,15 @@ const bindEvents = () => { $projectPath.val($projectPath.val().trim()); }); - function updateUrlPathWarningVisibility() { - const url = $projectImportUrl.val(); - const URL_PATTERN = /(?:git|https?):\/\/.*\/.*\.git$/; - const isUrlValid = URL_PATTERN.test(url); - $projectImportUrlWarning.toggleClass('hide', isUrlValid); - } + const updateUrlPathWarningVisibility = debounce(async () => { + const { success: isUrlValid } = await validateImportCredentials( + $projectImportUrl.val(), + $projectImportUrlUser.val(), + $projectImportUrlPassword.val(), + ); + $projectImportUrl.toggleClass(invalidInputClass, !isUrlValid); + $projectImportUrlError.toggleClass('hide', isUrlValid); + }, 500); let isProjectImportUrlDirty = false; $projectImportUrl.on('blur', () => { @@ -153,9 +178,22 @@ const bindEvents = () => { }); $projectImportUrl.on('keyup', () => { deriveProjectPathFromUrl($projectImportUrl); - // defer error message till first input blur - if (isProjectImportUrlDirty) { - updateUrlPathWarningVisibility(); + }); + + [$projectImportUrl, $projectImportUrlUser, $projectImportUrlPassword].forEach(($f) => { + $f.on('input', () => { + if (isProjectImportUrlDirty) { + updateUrlPathWarningVisibility(); + } + }); + }); + + $projectImportForm.on('submit', (e) => { + const $invalidFields = $projectImportForm.find(`.${invalidInputClass}`); + if ($invalidFields.length > 0) { + $invalidFields[0].focus(); + e.preventDefault(); + e.stopPropagation(); } }); |