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 +++++++++++++- 6 files changed, 189 insertions(+), 42 deletions(-) (limited to 'app/assets/javascripts') 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); +}; -- cgit v1.2.3