From f697dc5e76dfc5894df006d53b2b7e751653cf05 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 14 Apr 2020 18:09:54 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../components/ci_variable_modal.vue | 16 ++-- .../components/ci_variable_table.vue | 7 +- app/assets/javascripts/lib/utils/text_utility.js | 8 ++ .../javascripts/releases/components/app_edit.vue | 24 +++-- .../releases/components/asset_links_form.vue | 106 +++++++++++++++++---- .../releases/stores/modules/detail/getters.js | 70 +++++++++++++- app/controllers/import/github_controller.rb | 10 +- app/models/jira_import_state.rb | 2 + app/models/project.rb | 10 +- app/services/jira_import/start_import_service.rb | 18 ++++ 10 files changed, 223 insertions(+), 48 deletions(-) (limited to 'app') 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 32fe841f16e..316408adfb2 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 @@ -65,12 +65,6 @@ export default { modalActionText() { return this.variableBeingEdited ? __('Update variable') : __('Add variable'); }, - primaryAction() { - return { - text: this.modalActionText, - attributes: { variant: 'success', disabled: !this.canSubmit }, - }; - }, maskedFeedback() { return __('This variable can not be masked'); }, @@ -120,6 +114,8 @@ export default { ref="modal" :modal-id="$options.modalId" :title="modalActionText" + static + lazy @hidden="resetModalHandler" >
@@ -127,7 +123,7 @@ export default { @@ -142,7 +138,7 @@ export default { v-model="variableData.secret_value" rows="3" max-rows="6" - data-qa-selector="variable_value" + data-qa-selector="ci_variable_value_field" /> @@ -189,7 +185,7 @@ export default { {{ __('Mask variable') }} @@ -218,6 +214,7 @@ export default { ref="deleteCiVariable" category="secondary" variant="danger" + data-qa-selector="ci_variable_delete_button" @click="deleteVarAndClose" >{{ __('Delete variable') }} @@ -225,6 +222,7 @@ export default { ref="updateOrAddVariable" :disabled="!canSubmit" variant="success" + data-qa-selector="ci_variable_save_button" @click="updateOrAddVariable" >{{ modalActionText }} diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue index b374d950c1f..7eb791f97e4 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue @@ -26,7 +26,6 @@ export default { { key: 'value', label: s__('CiVariables|Value'), - tdClass: 'qa-ci-variable-input-value', customStyle: { width: '40%' }, }, { @@ -89,6 +88,7 @@ export default { :fields="fields" :items="variables" tbody-tr-class="js-ci-variable-row" + data-qa-selector="ci_variable_table_content" sort-by="key" sort-direction="asc" stacked="lg" @@ -150,6 +150,7 @@ export default { @@ -168,7 +169,7 @@ export default { {{ valuesButtonText }}{{ __('Add Variable') }} diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index f857e618d89..86714471823 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -232,3 +232,11 @@ export const truncateNamespace = (string = '') => { return namespace; }; + +/** + * Tests that the input is a String and has at least + * one non-whitespace character + * @param {String} obj The object to test + * @returns {Boolean} + */ +export const hasContent = obj => isString(obj) && obj.trim() !== ''; diff --git a/app/assets/javascripts/releases/components/app_edit.vue b/app/assets/javascripts/releases/components/app_edit.vue index 06e388002e4..df356c18417 100644 --- a/app/assets/javascripts/releases/components/app_edit.vue +++ b/app/assets/javascripts/releases/components/app_edit.vue @@ -1,6 +1,6 @@ @@ -69,60 +102,93 @@ export default {

{{ __( - 'Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance.', + 'Point to any links you like: documentation, built binaries, or other related materials. These can be internal or external links from your GitLab instance. Duplicate URLs are not allowed.', ) }}

+ + - - - {{ __('Remove asset link') }} - +
+ + + {{ __('Remove asset link') }} + +
- {{ __('Add another link') }} - + diff --git a/app/assets/javascripts/releases/stores/modules/detail/getters.js b/app/assets/javascripts/releases/stores/modules/detail/getters.js index 562284dc48d..84dc2fca4be 100644 --- a/app/assets/javascripts/releases/stores/modules/detail/getters.js +++ b/app/assets/javascripts/releases/stores/modules/detail/getters.js @@ -1,9 +1,13 @@ +import { isEmpty } from 'lodash'; +import { hasContent } from '~/lib/utils/text_utility'; + /** + * @param {Object} link The link to test * @returns {Boolean} `true` if the release link is empty, i.e. it has * empty (or whitespace-only) values for both `url` and `name`. * Otherwise, `false`. */ -const isEmptyReleaseLink = l => !/\S/.test(l.url) && !/\S/.test(l.name); +const isEmptyReleaseLink = link => !hasContent(link.url) && !hasContent(link.name); /** Returns all release links that aren't empty */ export const releaseLinksToCreate = state => { @@ -22,3 +26,67 @@ export const releaseLinksToDelete = state => { return state.originalRelease.assets.links; }; + +/** Returns all validation errors on the release object */ +export const validationErrors = state => { + const errors = { + assets: { + links: {}, + }, + }; + + if (!state.release) { + return errors; + } + + // Each key of this object is a URL, and the value is an + // array of Release link objects that share this URL. + // This is used for detecting duplicate URLs. + const urlToLinksMap = new Map(); + + state.release.assets.links.forEach(link => { + errors.assets.links[link.id] = {}; + + // Only validate non-empty URLs + if (isEmptyReleaseLink(link)) { + return; + } + + if (!hasContent(link.url)) { + errors.assets.links[link.id].isUrlEmpty = true; + } + + if (!hasContent(link.name)) { + errors.assets.links[link.id].isNameEmpty = true; + } + + const normalizedUrl = link.url.trim().toLowerCase(); + + // Compare each URL to every other URL and flag any duplicates + if (urlToLinksMap.has(normalizedUrl)) { + // a duplicate URL was found! + + // add a validation error for each link that shares this URL + const duplicates = urlToLinksMap.get(normalizedUrl); + duplicates.push(link); + duplicates.forEach(duplicateLink => { + errors.assets.links[duplicateLink.id].isDuplicate = true; + }); + } else { + // no duplicate URL was found + + urlToLinksMap.set(normalizedUrl, [link]); + } + + if (!/^(http|https|ftp):\/\//.test(normalizedUrl)) { + errors.assets.links[link.id].isBadFormat = true; + } + }); + + return errors; +}; + +/** Returns whether or not the release object is valid */ +export const isValid = (_state, getters) => { + return Object.values(getters.validationErrors.assets.links).every(isEmpty); +}; diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index c418b11ab13..34af1ecd6a5 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -9,6 +9,7 @@ class Import::GithubController < Import::BaseController before_action :expire_etag_cache, only: [:status, :create] rescue_from Octokit::Unauthorized, with: :provider_unauthorized + rescue_from Octokit::TooManyRequests, with: :provider_rate_limit def new if !ci_cd_only? && github_import_configured? && logged_in_with_provider? @@ -142,6 +143,13 @@ class Import::GithubController < Import::BaseController alert: "Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account." end + def provider_rate_limit(exception) + reset_time = Time.at(exception.response_headers['x-ratelimit-reset'].to_i) + session[access_token_key] = nil + redirect_to new_import_url, + alert: _("GitHub API rate limit exceeded. Try again after %{reset_time}") % { reset_time: reset_time } + end + def access_token_key :"#{provider}_access_token" end @@ -180,7 +188,7 @@ class Import::GithubController < Import::BaseController end def client_options - {} + { wait_for_rate_limit_reset: false } end def extra_import_params diff --git a/app/models/jira_import_state.rb b/app/models/jira_import_state.rb index ec1b8f03d36..543ee77917c 100644 --- a/app/models/jira_import_state.rb +++ b/app/models/jira_import_state.rb @@ -12,6 +12,8 @@ class JiraImportState < ApplicationRecord belongs_to :user belongs_to :label + scope :by_jira_project_key, -> (jira_project_key) { where(jira_project_key: jira_project_key) } + validates :project, presence: true validates :jira_project_key, presence: true validates :jira_project_name, presence: true diff --git a/app/models/project.rb b/app/models/project.rb index ee4cc6157eb..443b44dd023 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1190,14 +1190,14 @@ class Project < ApplicationRecord end def external_issue_tracker - if has_external_issue_tracker.nil? # To populate existing projects + if has_external_issue_tracker.nil? cache_has_external_issue_tracker end if has_external_issue_tracker? - return @external_issue_tracker if defined?(@external_issue_tracker) - - @external_issue_tracker = services.external_issue_trackers.first + strong_memoize(:external_issue_tracker) do + services.external_issue_trackers.first + end else nil end @@ -1217,7 +1217,7 @@ class Project < ApplicationRecord def external_wiki if has_external_wiki.nil? - cache_has_external_wiki # Populate + cache_has_external_wiki end if has_external_wiki diff --git a/app/services/jira_import/start_import_service.rb b/app/services/jira_import/start_import_service.rb index 6accc3cfffc..e8d9e6734bd 100644 --- a/app/services/jira_import/start_import_service.rb +++ b/app/services/jira_import/start_import_service.rb @@ -33,8 +33,10 @@ module JiraImport end def build_jira_import + label = create_import_label(project) project.jira_imports.build( user: user, + label: label, jira_project_key: jira_project_key, # we do not have the jira_project_name or jira_project_xid yet so just set a mock value, # we will once https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28190 @@ -43,6 +45,22 @@ module JiraImport ) end + def create_import_label(project) + label = ::Labels::CreateService.new(build_label_attrs(project)).execute(project: project) + raise Projects::ImportService::Error, _('Failed to create import label for jira import.') if label.blank? + + label + end + + def build_label_attrs(project) + import_start_time = Time.zone.now + jira_imports_for_project = project.jira_imports.by_jira_project_key(jira_project_key).size + 1 + title = "jira-import::#{jira_project_key}-#{jira_imports_for_project}" + description = "Label for issues that were imported from jira on #{import_start_time.strftime('%Y-%m-%d %H:%M:%S')}" + color = "#{Label.color_for(title)}" + { title: title, description: description, color: color } + end + def validate return build_error_response(_('Jira import feature is disabled.')) unless project.jira_issues_import_feature_flag_enabled? return build_error_response(_('You do not have permissions to run the import.')) unless user.can?(:admin_project, project) -- cgit v1.2.3