diff options
author | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
commit | 6438df3a1e0fb944485cebf07976160184697d72 (patch) | |
tree | 00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /app/assets/javascripts/vue_shared | |
parent | 42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff) |
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/vue_shared')
88 files changed, 535 insertions, 420 deletions
diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue index cb4c5f20377..2dc2c27f7ea 100644 --- a/app/assets/javascripts/vue_shared/components/actions_button.vue +++ b/app/assets/javascripts/vue_shared/components/actions_button.vue @@ -43,7 +43,7 @@ export default { return this.actions.length > 1; }, selectedAction() { - return this.actions.find(x => x.key === this.selectedKey) || this.actions[0]; + return this.actions.find((x) => x.key === this.selectedKey) || this.actions[0]; }, }, methods: { diff --git a/app/assets/javascripts/vue_shared/components/alert_details_table.vue b/app/assets/javascripts/vue_shared/components/alert_details_table.vue index 3e2b4cd35ab..655b867574d 100644 --- a/app/assets/javascripts/vue_shared/components/alert_details_table.vue +++ b/app/assets/javascripts/vue_shared/components/alert_details_table.vue @@ -49,7 +49,8 @@ export default { label: s__('AlertManagement|Key'), thClass, tdClass, - formatter: string => capitalizeFirstCharacter(convertToSentenceCase(splitCamelCase(string))), + formatter: (string) => + capitalizeFirstCharacter(convertToSentenceCase(splitCamelCase(string))), }, { key: 'value', diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue index 9a6433963bc..c1da2b8c305 100644 --- a/app/assets/javascripts/vue_shared/components/awards_list.vue +++ b/app/assets/javascripts/vue_shared/components/awards_list.vue @@ -48,7 +48,7 @@ export default { groupedAwards() { const { thumbsup, thumbsdown, ...rest } = { ...this.groupedDefaultAwards, - ...groupBy(this.awards, x => x.name), + ...groupBy(this.awards, (x) => x.name), }; return [ @@ -73,7 +73,7 @@ export default { return false; } - return awardList.some(award => award.user.id === this.currentUserId); + return awardList.some((award) => award.user.id === this.currentUserId); }, createAwardList(name, list) { return { @@ -95,11 +95,11 @@ export default { // Filter myself from list if I am awarded. if (hasReactionByCurrentUser) { - awardList = awardList.filter(award => award.user.id !== this.currentUserId); + awardList = awardList.filter((award) => award.user.id !== this.currentUserId); } // Get only 9-10 usernames to show in tooltip text. - const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map(award => award.user.name); + const namesToShow = awardList.slice(0, TOOLTIP_NAME_COUNT).map((award) => award.user.name); // Get the remaining list to use in `and x more` text. const remainingAwardList = awardList.slice(TOOLTIP_NAME_COUNT, awardList.length); diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue index f28e49df56e..14e99977a85 100644 --- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue @@ -79,6 +79,8 @@ export default { :title="tooltipTitle" :class="{ 'ml-auto': isCentered }" class="file-changed-icon d-inline-block" + data-qa-selector="changed_file_icon_content" + :data-qa-title="tooltipTitle" > <gl-icon v-if="showIcon" :name="changedIcon" :size="size" :class="changedIconClass" /> </span> diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue index e01a651806d..deca934e283 100644 --- a/app/assets/javascripts/vue_shared/components/commit.vue +++ b/app/assets/javascripts/vue_shared/components/commit.vue @@ -54,7 +54,7 @@ export default { type: Object, required: false, default: undefined, - validator: ref => + validator: (ref) => ref === undefined || (Number.isFinite(ref.iid) && isString(ref.path) && !isEmpty(ref.path)), }, @@ -107,7 +107,7 @@ export default { }, computed: { /** - * Determines if we shoud render the ref info section based + * Determines if we should render the ref info section based */ shouldShowRefInfo() { return this.showRefInfo && (this.commitRef || this.mergeRequestRef); diff --git a/app/assets/javascripts/vue_shared/components/confirm_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_modal.vue index 96f800511d2..7c1d3772acd 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_modal.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_modal.vue @@ -30,8 +30,8 @@ export default { }; }, mounted() { - document.querySelectorAll(this.selector).forEach(button => { - button.addEventListener('click', e => { + document.querySelectorAll(this.selector).forEach((button) => { + button.addEventListener('click', (e) => { e.preventDefault(); this.path = button.dataset.path; diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue index 5ac30424f98..9ff35132ac9 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/content_viewer.vue @@ -11,7 +11,7 @@ export default { }, props: { content: { - type: String, + type: [String, ArrayBuffer], default: '', required: false, }, @@ -54,7 +54,7 @@ export default { </script> <template> - <div class="preview-container"> + <div class="preview-container" data-qa-selector="preview_container"> <image-viewer v-if="type === 'image'" :path="path" :file-size="fileSize" /> <markdown-viewer v-if="type === 'markdown'" diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue index 8d55701f499..af85a2fda06 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue @@ -39,11 +39,16 @@ export default { <div class="file-content"> <p class="gl-mt-3 file-info"> {{ fileName }} - <template v-if="fileSize > 0"> - ({{ fileSizeReadable }}) - </template> + <template v-if="fileSize > 0"> ({{ fileSizeReadable }}) </template> </p> - <a :href="path" class="btn btn-default" rel="nofollow" :download="fileName" target="_blank"> + <a + :href="path" + class="btn btn-default" + rel="nofollow" + :download="fileName" + target="_blank" + data-qa-selector="download_button" + > <gl-icon :size="16" name="download" class="float-left gl-mr-3" /> {{ __('Download') }} </a> diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue index eb7e24734ce..9ece6a52805 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue @@ -82,7 +82,7 @@ export default { </script> <template> - <div data-testid="image-viewer"> + <div data-testid="image-viewer" data-qa-selector="image_viewer_container"> <div :class="innerCssClasses" class="position-relative"> <img ref="contentImg" :src="path" @load="onImgLoad" /> <slot @@ -95,9 +95,7 @@ export default { <template v-if="hasFileSize"> {{ fileSizeReadable }} </template> - <template v-if="hasFileSize && hasDimensions"> - | - </template> + <template v-if="hasFileSize && hasDimensions"> | </template> <template v-if="hasDimensions"> <strong>{{ s__('ImageViewerDimensions|W') }}</strong >: {{ width }} | <strong>{{ s__('ImageViewerDimensions|H') }}</strong diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue index 67be76604a3..24386c90954 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue @@ -111,6 +111,6 @@ export default { <template> <div ref="markdownPreview" class="md-previewer" data-testid="md-previewer"> <gl-skeleton-loading v-if="isLoading" /> - <div v-else class="md" v-html="previewContent"></div> + <div v-else class="md gl-ml-auto gl-mr-auto" v-html="previewContent"></div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue index 79cdf308ac5..d0c2672b162 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker.vue @@ -202,7 +202,7 @@ export default { <template> <tooltip-on-truncate :title="timeWindowText" - :truncate-target="elem => elem.querySelector('.gl-dropdown-toggle-text')" + :truncate-target="(elem) => elem.querySelector('.gl-dropdown-toggle-text')" placement="top" class="d-inline-block" > diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_input.vue b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_input.vue index 32a24844d71..39c1caf928e 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_input.vue +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_input.vue @@ -20,12 +20,12 @@ export default { state: { default: null, required: true, - validator: prop => typeof prop === 'boolean' || prop === null, + validator: (prop) => typeof prop === 'boolean' || prop === null, }, value: { default: null, required: false, - validator: prop => typeof prop === 'string' || prop === null, + validator: (prop) => typeof prop === 'string' || prop === null, }, label: { type: String, diff --git a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js index aaadc9766db..aec67a18a05 100644 --- a/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js +++ b/app/assets/javascripts/vue_shared/components/date_time_picker/date_time_picker_lib.js @@ -25,7 +25,7 @@ export const defaultTimeRanges = [ }, ]; -export const defaultTimeRange = defaultTimeRanges.find(tr => tr.default); +export const defaultTimeRange = defaultTimeRanges.find((tr) => tr.default); export const dateFormats = { /** @@ -49,7 +49,7 @@ export const dateFormats = { * @param {string} value - Value as typed by the user * @returns true if the value can be parsed as a valid date, false otherwise */ -export const isValidInputString = value => { +export const isValidInputString = (value) => { try { // dateformat throws error that can be caught. // This is better than using `new Date()` diff --git a/app/assets/javascripts/vue_shared/components/deployment_instance.vue b/app/assets/javascripts/vue_shared/components/deployment_instance.vue new file mode 100644 index 00000000000..41b783aa011 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/deployment_instance.vue @@ -0,0 +1,91 @@ +<script> +/** + * An instance in deploy board is represented by a square in this mockup: + * https://gitlab.com/gitlab-org/gitlab-foss/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png + * + * Each instance has a state and a tooltip. + * The state needs to be represented in different colors, + * see more information about this in + * https://gitlab.com/gitlab-org/gitlab/uploads/f1f00df6293d30f241dbeaa876a1e939/Screen_Shot_2019-11-26_at_3.35.43_PM.png + * + * An instance can represent a normal deploy or a canary deploy. In the latter we need to provide + * this information in the tooltip and the colors. + * Mockup is https://gitlab.com/gitlab-org/gitlab/issues/35570 + */ +import { GlLink, GlTooltipDirective } from '@gitlab/ui'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; + +export default { + components: { + GlLink, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + + props: { + /** + * Represents the status of the pod. Each state is represented with a different + * color. + * It should be one of the following: + * succeeded || running || failed || pending || unknown + */ + status: { + type: String, + required: true, + default: 'succeeded', + }, + + tooltipText: { + type: String, + required: false, + default: '', + }, + + stable: { + type: Boolean, + required: false, + default: true, + }, + + podName: { + type: String, + required: false, + default: '', + }, + + logsPath: { + type: String, + required: false, + default: '', + }, + }, + + computed: { + isLink() { + return this.logsPath !== '' && this.podName !== ''; + }, + + cssClass() { + return { + [`deployment-instance-${this.status}`]: true, + 'deployment-instance-canary': !this.stable, + link: this.isLink, + }; + }, + + computedLogPath() { + return this.isLink ? mergeUrlParams({ pod_name: this.podName }, this.logsPath) : null; + }, + }, +}; +</script> +<template> + <gl-link + v-gl-tooltip + :class="cssClass" + :title="tooltipText" + :href="computedLogPath" + class="deployment-instance d-flex justify-content-center align-items-center" + /> +</template> diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue deleted file mode 100644 index c4bce860ae4..00000000000 --- a/app/assets/javascripts/vue_shared/components/deprecated_modal_2.vue +++ /dev/null @@ -1,121 +0,0 @@ -<script> -import $ from 'jquery'; -import { GlButton } from '@gitlab/ui'; - -const buttonVariants = ['danger', 'primary', 'success', 'warning']; -const sizeVariants = ['sm', 'md', 'lg', 'xl']; - -export default { - name: 'DeprecatedModal2', // use GlModal instead - - components: { - GlButton, - }, - props: { - id: { - type: String, - required: false, - default: null, - }, - modalSize: { - type: String, - required: false, - default: 'md', - validator: value => sizeVariants.includes(value), - }, - headerTitleText: { - type: String, - required: false, - default: '', - }, - footerPrimaryButtonVariant: { - type: String, - required: false, - default: 'primary', - validator: value => buttonVariants.includes(value), - }, - footerPrimaryButtonText: { - type: String, - required: false, - default: '', - }, - }, - computed: { - modalSizeClass() { - return this.modalSize === 'md' ? '' : `modal-${this.modalSize}`; - }, - }, - mounted() { - $(this.$el) - .on('shown.bs.modal', this.opened) - .on('hidden.bs.modal', this.closed); - }, - beforeDestroy() { - $(this.$el) - .off('shown.bs.modal', this.opened) - .off('hidden.bs.modal', this.closed); - }, - methods: { - emitCancel(event) { - this.$emit('cancel', event); - }, - emitSubmit(event) { - this.$emit('submit', event); - }, - opened() { - this.$emit('open'); - }, - closed() { - this.$emit('closed'); - }, - }, -}; -</script> - -<template> - <div :id="id" class="modal fade" tabindex="-1" role="dialog"> - <div :class="modalSizeClass" class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header gl-pr-4"> - <slot name="header"> - <h4 class="modal-title"> - <slot name="title"> {{ headerTitleText }} </slot> - </h4> - <gl-button - :aria-label="s__('Modal|Close')" - variant="default" - category="tertiary" - size="small" - icon="close" - class="js-modal-close-action" - data-dismiss="modal" - @click="emitCancel($event)" - /> - </slot> - </div> - - <div class="modal-body"><slot></slot></div> - - <div class="modal-footer"> - <slot name="footer"> - <gl-button - class="js-modal-cancel-action qa-modal-cancel-button" - data-dismiss="modal" - @click="emitCancel($event)" - > - {{ s__('Modal|Cancel') }} - </gl-button> - <gl-button - :class="`btn-${footerPrimaryButtonVariant}`" - class="js-modal-primary-action qa-modal-primary-button" - data-dismiss="modal" - @click="emitSubmit($event)" - > - {{ footerPrimaryButtonText }} - </gl-button> - </slot> - </div> - </div> - </div> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/dismissible_container.vue b/app/assets/javascripts/vue_shared/components/dismissible_container.vue index 6d5fd065751..2eaf833a6be 100644 --- a/app/assets/javascripts/vue_shared/components/dismissible_container.vue +++ b/app/assets/javascripts/vue_shared/components/dismissible_container.vue @@ -22,7 +22,7 @@ export default { .post(this.path, { feature_name: this.featureId, }) - .catch(e => { + .catch((e) => { // eslint-disable-next-line @gitlab/require-i18n-strings, no-console console.error('Failed to dismiss message.', e); }); diff --git a/app/assets/javascripts/vue_shared/components/editor_lite.vue b/app/assets/javascripts/vue_shared/components/editor_lite.vue index cfe3ce0a11c..7218b84cf8a 100644 --- a/app/assets/javascripts/vue_shared/components/editor_lite.vue +++ b/app/assets/javascripts/vue_shared/components/editor_lite.vue @@ -84,6 +84,9 @@ export default { onFileChange() { this.$emit('input', this.editor.getValue()); }, + getEditor() { + return this.editor; + }, }, }; </script> diff --git a/app/assets/javascripts/vue_shared/components/file_finder/index.vue b/app/assets/javascripts/vue_shared/components/file_finder/index.vue index 05403b38850..27933f87929 100644 --- a/app/assets/javascripts/vue_shared/components/file_finder/index.vue +++ b/app/assets/javascripts/vue_shared/components/file_finder/index.vue @@ -128,7 +128,7 @@ export default { this.focusedIndex = 0; } - Mousetrap.bind(['t', 'mod+p'], e => { + Mousetrap.bind(['t', 'mod+p'], (e) => { if (e.preventDefault) { e.preventDefault(); } diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js index 91a0ac3aa92..f7cfb59be01 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -593,15 +593,6 @@ const fileNameIcons = { export default function getIconForFile(name) { return ( - fileNameIcons[name] || - fileExtensionIcons[ - name - ? name - .split('.') - .pop() - .toLowerCase() - : '' - ] || - '' + fileNameIcons[name] || fileExtensionIcons[name ? name.split('.').pop().toLowerCase() : ''] || '' ); } diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue index 4d07d9fcfdd..96567111bbc 100644 --- a/app/assets/javascripts/vue_shared/components/file_row.vue +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -137,7 +137,11 @@ export default { @click="clickFile" @mouseleave="$emit('mouseleave', $event)" > - <div class="file-row-name-container"> + <div + class="file-row-name-container" + data-qa-selector="file_row_container" + :data-qa-file-name="file.name" + > <span ref="textOutput" :style="levelIndentation" diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue index 3988b3814f9..a4c5ca28494 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue @@ -59,7 +59,7 @@ export default { type: String, required: false, default: '', - validator: value => value === '' || /(_desc)|(_asc)/g.test(value), + validator: (value) => value === '' || /(_desc)|(_asc)/g.test(value), }, showCheckbox: { type: Boolean, @@ -89,7 +89,7 @@ export default { if (this.initialSortBy) { selectedSortOption = this.sortOptions .filter( - sortBy => + (sortBy) => sortBy.sortDirection.ascending === this.initialSortBy || sortBy.sortDirection.descending === this.initialSortBy, ) @@ -204,12 +204,12 @@ export default { this.recentSearchesStore = new RecentSearchesStore({ isLocalStorageAvailable: RecentSearchesService.isAvailable(), - allowedKeys: this.tokens.map(token => token.type), + allowedKeys: this.tokens.map((token) => token.type), }); this.recentSearchesPromise = this.recentSearchesService .fetch() - .catch(error => { + .catch((error) => { if (error.name === 'RecentSearchesServiceError') return undefined; createFlash(__('An error occurred while parsing recent searches')); @@ -217,7 +217,7 @@ export default { // Gracefully fail to empty array return []; }) - .then(searches => { + .then((searches) => { if (!searches) return; // Put any searches that may have come in before @@ -250,13 +250,13 @@ export default { * spaces. */ removeQuotesEnclosure(filters = []) { - return filters.map(filter => { + return filters.map((filter) => { if (typeof filter === 'object') { const valueString = filter.value.data; return { ...filter, value: { - data: stripQuotes(valueString), + data: typeof valueString === 'string' ? stripQuotes(valueString) : valueString, operator: filter.value.operator, }, }; @@ -305,8 +305,8 @@ export default { }, historyTokenOptionTitle(historyToken) { const tokenOption = this.tokens - .find(token => token.type === historyToken.type) - ?.options?.find(option => option.value === historyToken.value.data); + .find((token) => token.type === historyToken.type) + ?.options?.find((option) => option.value === historyToken.value.data); if (!tokenOption?.title) { return historyToken.value.data; diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js index e7d7b7d9f1b..a15cf220ee5 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js @@ -8,7 +8,7 @@ import { queryToObject } from '~/lib/utils/url_utility'; * * @returns {String} String without any enclosure */ -export const stripQuotes = value => value.replace(/^('|")(.*)('|")$/, '$2'); +export const stripQuotes = (value) => value.replace(/^('|")(.*)('|")$/, '$2'); /** * This method removes duplicate tokens from tokens array. @@ -17,7 +17,7 @@ export const stripQuotes = value => value.replace(/^('|")(.*)('|")$/, '$2'); * * @returns {Array} Unique array of tokens */ -export const uniqueTokens = tokens => { +export const uniqueTokens = (tokens) => { const knownTokens = []; return tokens.reduce((uniques, token) => { if (typeof token === 'object' && token.type !== 'filtered-search-term') { @@ -61,7 +61,7 @@ export function prepareTokens(filters = {}) { return memo; } if (Array.isArray(value)) { - return [...memo, ...value.map(filterValue => createToken(key, filterValue))]; + return [...memo, ...value.map((filterValue) => createToken(key, filterValue))]; } return [...memo, createToken(key, value)]; @@ -99,8 +99,8 @@ export function filterToQueryObject(filters = {}) { let selected; let unselected; if (Array.isArray(filter)) { - selected = filter.filter(item => item.operator === '=').map(item => item.value); - unselected = filter.filter(item => item.operator === '!=').map(item => item.value); + selected = filter.filter((item) => item.operator === '=').map((item) => item.value); + unselected = filter.filter((item) => item.operator === '!=').map((item) => item.value); } else { selected = filter?.operator === '=' ? filter.value : null; unselected = filter?.operator === '!=' ? filter.value : null; @@ -155,7 +155,7 @@ export function urlQueryToFilter(query = '') { previousValues = memo[filterName]; } if (Array.isArray(value)) { - const newAdditions = value.filter(Boolean).map(item => ({ value: item, operator })); + const newAdditions = value.filter(Boolean).map((item) => ({ value: item, operator })); return { ...memo, [filterName]: [...previousValues, ...newAdditions] }; } diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/store/modules/filters/actions.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/store/modules/filters/actions.js index 443cb28cf10..411654d15f4 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/store/modules/filters/actions.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/store/modules/filters/actions.js @@ -17,7 +17,7 @@ export function fetchBranches({ commit, state }, search = '') { commit(types.REQUEST_BRANCHES); return Api.branches(projectEndpoint, search) - .then(response => { + .then((response) => { commit(types.RECEIVE_BRANCHES_SUCCESS, response.data); return response; }) @@ -34,7 +34,7 @@ export const fetchMilestones = ({ commit, state }, search_title = '') => { return axios .get(milestonesEndpoint, { params: { search_title } }) - .then(response => { + .then((response) => { commit(types.RECEIVE_MILESTONES_SUCCESS, response.data); return response; }) @@ -50,7 +50,7 @@ export const fetchLabels = ({ commit, state }, search = '') => { return axios .get(state.labelsEndpoint, { params: { search } }) - .then(response => { + .then((response) => { commit(types.RECEIVE_LABELS_SUCCESS, response.data); return response; }) @@ -67,13 +67,13 @@ function fetchUser(options = {}) { let fetchUserPromise; if (projectEndpoint) { - fetchUserPromise = Api.projectUsers(projectEndpoint, query).then(data => ({ data })); + fetchUserPromise = Api.projectUsers(projectEndpoint, query).then((data) => ({ data })); } else { fetchUserPromise = Api.groupMembers(groupEndpoint, { query }); } return fetchUserPromise - .then(response => { + .then((response) => { commit(`RECEIVE_${action}_SUCCESS`, response.data); return response; }) diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue index ee0e00b0f5d..d53c829a48e 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue @@ -43,7 +43,7 @@ export default { return this.value.data.toLowerCase(); }, activeAuthor() { - return this.authors.find(author => author.username.toLowerCase() === this.currentValue); + return this.authors.find((author) => author.username.toLowerCase() === this.currentValue); }, }, watch: { @@ -63,7 +63,7 @@ export default { : this.config.fetchAuthors(searchTerm); fetchPromise - .then(res => { + .then((res) => { // We'd want to avoid doing this check but // users.json and /groups/:id/members & /projects/:id/users // return response differently. diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue index c18bdfc5c20..694dcd95b5e 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/branch_token.vue @@ -43,7 +43,7 @@ export default { return this.value.data.toLowerCase(); }, activeBranch() { - return this.branches.find(branch => branch.name.toLowerCase() === this.currentValue); + return this.branches.find((branch) => branch.name.toLowerCase() === this.currentValue); }, }, watch: { diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue index 7a9c5c277eb..d59e9200e6c 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/label_token.vue @@ -47,7 +47,7 @@ export default { }, activeLabel() { return this.labels.find( - label => label.title.toLowerCase() === stripQuotes(this.currentValue), + (label) => label.title.toLowerCase() === stripQuotes(this.currentValue), ); }, containerStyle() { @@ -74,7 +74,7 @@ export default { this.loading = true; this.config .fetchLabels(searchTerm) - .then(res => { + .then((res) => { // We'd want to avoid doing this check but // labels.json and /groups/:id/labels & /projects/:id/labels // return response differently. diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue index c24df5e081d..0dd7820073a 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue @@ -43,7 +43,7 @@ export default { }, activeMilestone() { return this.milestones.find( - milestone => milestone.title.toLowerCase() === stripQuotes(this.currentValue), + (milestone) => milestone.title.toLowerCase() === stripQuotes(this.currentValue), ); }, }, diff --git a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue index 1ad0ca36bf8..9ab91e567e6 100644 --- a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue +++ b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue @@ -4,6 +4,7 @@ import { GfmAutocompleteType, tributeConfig, } from 'ee_else_ce/vue_shared/components/gfm_autocomplete/utils'; +import * as Emoji from '~/emoji'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; @@ -27,7 +28,7 @@ export default { }, computed: { config() { - return this.autocompleteTypes.map(type => ({ + return this.autocompleteTypes.map((type) => ({ ...tributeConfig[type].config, loadingItemTemplate: `<span class="gl-spinner gl-vertical-align-text-bottom gl-ml-3 gl-mr-2"></span>${__( 'Loading', @@ -55,7 +56,7 @@ export default { if (!this.assignees || !isAssigneesLengthSame) { this.assignees = - SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || []; + SidebarMediator.singleton?.store?.assignees?.map((assignee) => assignee.username) || []; } }, filterValues(type) { @@ -76,10 +77,18 @@ export default { return (inputText, processValues) => { if (this.cache[type]) { processValues(this.filterValues(type)); + } else if (type === GfmAutocompleteType.Emojis) { + Emoji.initEmojiMap() + .then(() => { + const emojis = Emoji.getValidEmojiNames(); + this.cache[type] = emojis; + processValues(emojis); + }) + .catch(() => createFlash({ message: this.$options.errorMessage })); } else if (this.dataSources[type]) { axios .get(this.dataSources[type]) - .then(response => { + .then((response) => { this.cache[type] = response.data; processValues(this.filterValues(type)); }) diff --git a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js index 2581888b504..809932b0f29 100644 --- a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js +++ b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js @@ -1,16 +1,24 @@ import { escape, last } from 'lodash'; +import * as Emoji from '~/emoji'; import { spriteIcon } from '~/lib/utils/common_utils'; const groupType = 'Group'; // eslint-disable-line @gitlab/require-i18n-strings +// Number of users to show in the autocomplete menu to avoid doing a mass fetch of 100+ avatars +const memberLimit = 10; + const nonWordOrInteger = /\W|^\d+$/; +export const menuItemLimit = 100; + export const GfmAutocompleteType = { + Emojis: 'emojis', Issues: 'issues', Labels: 'labels', Members: 'members', MergeRequests: 'mergeRequests', Milestones: 'milestones', + QuickActions: 'commands', Snippets: 'snippets', }; @@ -21,10 +29,21 @@ function doesCurrentLineStartWith(searchString, fullText, selectionStart) { } export const tributeConfig = { + [GfmAutocompleteType.Emojis]: { + config: { + trigger: ':', + lookup: (value) => value, + menuItemLimit, + menuItemTemplate: ({ original }) => `${original} ${Emoji.glEmojiTag(original)}`, + selectTemplate: ({ original }) => `:${original}:`, + }, + }, + [GfmAutocompleteType.Issues]: { config: { trigger: '#', - lookup: value => `${value.iid}${value.title}`, + lookup: (value) => `${value.iid}${value.title}`, + menuItemLimit, menuItemTemplate: ({ original }) => `<small>${original.reference || original.iid}</small> ${escape(original.title)}`, selectTemplate: ({ original }) => original.reference || `#${original.iid}`, @@ -35,6 +54,7 @@ export const tributeConfig = { config: { trigger: '~', lookup: 'title', + menuItemLimit, menuItemTemplate: ({ original }) => ` <span class="dropdown-label-box" style="background: ${escape(original.color)};"></span> ${escape(original.title)}`, @@ -45,11 +65,11 @@ export const tributeConfig = { }, filterValues({ collection, fullText, selectionStart }) { if (doesCurrentLineStartWith('/label', fullText, selectionStart)) { - return collection.filter(label => !label.set); + return collection.filter((label) => !label.set); } if (doesCurrentLineStartWith('/unlabel', fullText, selectionStart)) { - return collection.filter(label => label.set); + return collection.filter((label) => label.set); } return collection; @@ -60,8 +80,9 @@ export const tributeConfig = { config: { trigger: '@', fillAttr: 'username', - lookup: value => + lookup: (value) => value.type === groupType ? last(value.name.split(' / ')) : `${value.name}${value.username}`, + menuItemLimit: memberLimit, menuItemTemplate: ({ original }) => { const commonClasses = 'gl-avatar gl-avatar-s24 gl-flex-shrink-0'; const noAvatarClasses = `${commonClasses} gl-rounded-small @@ -101,11 +122,11 @@ export const tributeConfig = { }, filterValues({ assignees, collection, fullText, selectionStart }) { if (doesCurrentLineStartWith('/assign', fullText, selectionStart)) { - return collection.filter(member => !assignees.includes(member.username)); + return collection.filter((member) => !assignees.includes(member.username)); } if (doesCurrentLineStartWith('/unassign', fullText, selectionStart)) { - return collection.filter(member => assignees.includes(member.username)); + return collection.filter((member) => assignees.includes(member.username)); } return collection; @@ -115,7 +136,8 @@ export const tributeConfig = { [GfmAutocompleteType.MergeRequests]: { config: { trigger: '!', - lookup: value => `${value.iid}${value.title}`, + lookup: (value) => `${value.iid}${value.title}`, + menuItemLimit, menuItemTemplate: ({ original }) => `<small>${original.reference || original.iid}</small> ${escape(original.title)}`, selectTemplate: ({ original }) => original.reference || `!${original.iid}`, @@ -126,16 +148,47 @@ export const tributeConfig = { config: { trigger: '%', lookup: 'title', + menuItemLimit, menuItemTemplate: ({ original }) => escape(original.title), selectTemplate: ({ original }) => `%"${escape(original.title)}"`, }, }, + [GfmAutocompleteType.QuickActions]: { + config: { + trigger: '/', + fillAttr: 'name', + lookup: (value) => `${value.name}${value.aliases.join()}`, + menuItemLimit, + menuItemTemplate: ({ original }) => { + const aliases = original.aliases.length + ? `<small>(or /${original.aliases.join(', /')})</small>` + : ''; + + const params = original.params.length ? `<small>${original.params.join(' ')}</small>` : ''; + + let description = ''; + + if (original.warning) { + const confidentialIcon = + original.icon === 'confidential' ? spriteIcon('eye-slash', 's16 gl-mr-2') : ''; + description = `<small>${confidentialIcon}<em>${original.warning}</em></small>`; + } else if (original.description) { + description = `<small><em>${original.description}</em></small>`; + } + + return `<div>/${original.name} ${aliases} ${params}</div> + <div>${description}</div>`; + }, + }, + }, + [GfmAutocompleteType.Snippets]: { config: { trigger: '$', fillAttr: 'id', - lookup: value => `${value.id}${value.title}`, + lookup: (value) => `${value.id}${value.title}`, + menuItemLimit, menuItemTemplate: ({ original }) => `<small>${original.id}</small> ${escape(original.title)}`, }, }, diff --git a/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js b/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js index 221c4f5b8a8..0a6b50674f0 100644 --- a/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js +++ b/app/assets/javascripts/vue_shared/components/lib/utils/diff_utils.js @@ -15,5 +15,5 @@ function cleanSuggestionLine(line = {}) { } export function selectDiffLines(lines) { - return lines.filter(line => line.type !== 'match').map(line => cleanSuggestionLine(line)); + return lines.filter((line) => line.type !== 'match').map((line) => cleanSuggestionLine(line)); } diff --git a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue index b9729a3dc5c..10887aee689 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue @@ -1,6 +1,5 @@ <script> import { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton } from '@gitlab/ui'; -import { __, sprintf } from '~/locale'; export default { components: { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton }, @@ -10,7 +9,7 @@ export default { required: false, default: false, }, - fileName: { + defaultCommitMessage: { type: String, required: true, }, @@ -18,18 +17,11 @@ export default { data() { return { message: null, - buttonText: __('Apply suggestion'), - headerText: __('Apply suggestion commit message'), }; }, - computed: { - placeholderText() { - return sprintf(__('Apply suggestion on %{fileName}'), { fileName: this.fileName }); - }, - }, methods: { onApply() { - this.$emit('apply', this.message || this.placeholderText); + this.$emit('apply', this.message); }, }, }; @@ -37,18 +29,26 @@ export default { <template> <gl-dropdown - :text="buttonText" - :header-text="headerText" + :text="__('Apply suggestion')" :disabled="disabled" boundary="window" right - menu-class="gl-w-full! gl-pb-0!" + menu-class="gl-w-full!" + @shown="$refs.commitMessage.$el.focus()" > - <gl-dropdown-form class="gl-m-3!"> - <gl-form-textarea v-model="message" :placeholder="placeholderText" /> + <gl-dropdown-form class="gl-px-4! gl-m-0!"> + <label for="commit-message">{{ __('Commit message') }}</label> + <gl-form-textarea + id="commit-message" + ref="commitMessage" + v-model="message" + :placeholder="defaultCommitMessage" + submit-on-enter + @submit="onApply" + /> <gl-button - class="gl-w-quarter! gl-mt-3 gl-text-center! float-right" - category="secondary" + class="gl-w-auto! gl-mt-3 gl-text-center! gl-hover-text-white! gl-transition-medium! float-right" + category="primary" variant="success" @click="onApply" > diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 232a3054cd0..b6e167524aa 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -158,7 +158,7 @@ export default { const mediaInPreview = this.$refs['markdown-preview'].querySelectorAll('video, audio'); if (mediaInPreview) { - mediaInPreview.forEach(media => { + mediaInPreview.forEach((media) => { media.pause(); }); } @@ -169,7 +169,7 @@ export default { return new GLForm( $(this.$refs['gl-form']), { - emojis: this.enableAutocomplete, + emojis: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, members: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, issues: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, mergeRequests: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, @@ -199,7 +199,7 @@ export default { this.markdownPreview = __('Loading…'); axios .post(this.markdownPreviewPath, { text: this.textareaValue }) - .then(response => this.renderMarkdown(response.data)) + .then((response) => this.renderMarkdown(response.data)) .catch(() => new Flash(__('Error loading markdown preview'))); } else { this.renderMarkdown(); diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index d0a0560846a..173d192dab0 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -110,7 +110,7 @@ export default { const area = this.$el.parentNode.querySelector('textarea'); CopyAsGFM.nodeToGFM(transformed) - .then(gfm => { + .then((gfm) => { CopyAsGFM.insertPastedText(area, documentFragment.textContent, CopyAsGFM.quoted(gfm)); }) .catch(() => {}); diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue index 13ec7a6ada9..93a270b8a97 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue @@ -27,6 +27,10 @@ export default { type: String, required: true, }, + defaultCommitMessage: { + type: String, + required: true, + }, suggestionsCount: { type: Number, required: false, @@ -47,8 +51,8 @@ export default { }, }, methods: { - applySuggestion(callback) { - this.$emit('apply', { suggestionId: this.suggestion.id, callback }); + applySuggestion(callback, message) { + this.$emit('apply', { suggestionId: this.suggestion.id, callback, message }); }, applySuggestionBatch() { this.$emit('applyBatch'); @@ -74,6 +78,7 @@ export default { :is-applying-batch="suggestion.is_applying_batch" :batch-suggestions-count="batchSuggestionsCount" :help-page-path="helpPagePath" + :default-commit-message="defaultCommitMessage" :inapplicable-reason="suggestion.inapplicable_reason" @apply="applySuggestion" @applyBatch="applySuggestionBatch" diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue index fb51840b689..63341b433e0 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue @@ -2,9 +2,10 @@ import { GlButton, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui'; import { __ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import ApplySuggestion from './apply_suggestion.vue'; export default { - components: { GlIcon, GlButton, GlLoadingIcon }, + components: { GlIcon, GlButton, GlLoadingIcon, ApplySuggestion }, directives: { 'gl-tooltip': GlTooltipDirective }, mixins: [glFeatureFlagsMixin()], props: { @@ -37,6 +38,10 @@ export default { type: String, required: true, }, + defaultCommitMessage: { + type: String, + required: true, + }, inapplicableReason: { type: String, required: false, @@ -57,6 +62,9 @@ export default { canBeBatched() { return Boolean(this.glFeatures.batchSuggestions); }, + canAddCustomCommitMessage() { + return this.glFeatures.suggestionsCustomCommit; + }, isApplying() { return this.isApplyingSingle || this.isApplyingBatch; }, @@ -77,10 +85,10 @@ export default { }, }, methods: { - applySuggestion() { + applySuggestion(message) { if (!this.canApply) return; this.isApplyingSingle = true; - this.$emit('apply', this.applySuggestionCallback); + this.$emit('apply', this.applySuggestionCallback, message); }, applySuggestionCallback() { this.isApplyingSingle = false; @@ -142,7 +150,14 @@ export default { > {{ __('Add suggestion to batch') }} </gl-button> - <span v-gl-tooltip.viewport="tooltipMessage" tabindex="0"> + <apply-suggestion + v-if="canAddCustomCommitMessage" + :disabled="isDisableButton" + :default-commit-message="defaultCommitMessage" + class="gl-ml-3" + @apply="applySuggestion" + /> + <span v-else v-gl-tooltip.viewport="tooltipMessage" tabindex="0"> <gl-button v-if="isLoggedIn" class="btn-inverted js-apply-btn btn-grouped" diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue index 927a93487e6..5ee51764555 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue @@ -38,6 +38,10 @@ export default { type: String, required: true, }, + defaultCommitMessage: { + type: String, + required: true, + }, suggestionsCount: { type: Number, required: false, @@ -82,27 +86,41 @@ export default { this.isRendered = true; }, generateDiff(suggestionIndex) { - const { suggestions, disabled, batchSuggestionsInfo, helpPagePath, suggestionsCount } = this; + const { + suggestions, + disabled, + batchSuggestionsInfo, + helpPagePath, + defaultCommitMessage, + suggestionsCount, + } = this; const suggestion = suggestions && suggestions[suggestionIndex] ? suggestions[suggestionIndex] : {}; const SuggestionDiffComponent = Vue.extend(SuggestionDiff); const suggestionDiff = new SuggestionDiffComponent({ - propsData: { disabled, suggestion, batchSuggestionsInfo, helpPagePath, suggestionsCount }, + propsData: { + disabled, + suggestion, + batchSuggestionsInfo, + helpPagePath, + defaultCommitMessage, + suggestionsCount, + }, }); - suggestionDiff.$on('apply', ({ suggestionId, callback }) => { - this.$emit('apply', { suggestionId, callback, flashContainer: this.$el }); + suggestionDiff.$on('apply', ({ suggestionId, callback, message }) => { + this.$emit('apply', { suggestionId, callback, flashContainer: this.$el, message }); }); suggestionDiff.$on('applyBatch', () => { this.$emit('applyBatch', { flashContainer: this.$el }); }); - suggestionDiff.$on('addToBatch', suggestionId => { + suggestionDiff.$on('addToBatch', (suggestionId) => { this.$emit('addToBatch', suggestionId); }); - suggestionDiff.$on('removeFromBatch', suggestionId => { + suggestionDiff.$on('removeFromBatch', (suggestionId) => { this.$emit('removeFromBatch', suggestionId); }); diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue index 5824cb9438f..15c5b9d6733 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue @@ -49,10 +49,10 @@ export default { ) " > - <template #markdownDocsLink="{content}"> + <template #markdownDocsLink="{ content }"> <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link> </template> - <template #quickActionsDocsLink="{content}"> + <template #quickActionsDocsLink="{ content }"> <gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> @@ -81,7 +81,7 @@ export default { ) " > - <template #retryButton="{content}"> + <template #retryButton="{ content }"> <gl-button variant="link" category="primary" @@ -90,7 +90,7 @@ export default { {{ content }} </gl-button> </template> - <template #newFileButton="{content}"> + <template #newFileButton="{ content }"> <gl-button variant="link" category="primary" diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue index de9c84dd157..e3a7f144321 100644 --- a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue +++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue @@ -55,6 +55,11 @@ export default { required: false, default: null, }, + category: { + type: String, + required: false, + default: 'primary', + }, }, computed: { modalDomId() { @@ -70,14 +75,14 @@ export default { document.body, }); this.clipboard - .on('success', e => { + .on('success', (e) => { this.$root.$emit('bv::hide::tooltip', this.id); this.$emit('success', e); // Clear the selection and blur the trigger so it loses its border e.clearSelection(); e.trigger.blur(); }) - .on('error', e => this.$emit('error', e)); + .on('error', (e) => this.$emit('error', e)); }); }, destroyed() { @@ -95,6 +100,7 @@ export default { :data-clipboard-target="target" :data-clipboard-text="text" :title="title" + :category="category" icon="copy-to-clipboard" /> </template> diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue index 3749888ee36..653ee7f20e9 100644 --- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue +++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue @@ -1,5 +1,6 @@ <script> import $ from 'jquery'; +import { GlBadge, GlTabs, GlTab } from '@gitlab/ui'; /** * Given an array of tabs, renders non linked bootstrap tabs. @@ -23,6 +24,11 @@ import $ from 'jquery'; */ export default { name: 'NavigationTabs', + components: { + GlBadge, + GlTabs, + GlTab, + }, props: { tabs: { type: Array, @@ -50,24 +56,21 @@ export default { }; </script> <template> - <ul class="nav-links scrolling-tabs separator"> - <li + <gl-tabs class="gl-display-flex gl-w-full" nav-class="gl-border-0!"> + <gl-tab v-for="(tab, i) in tabs" :key="i" - :class="{ - active: tab.isActive, - }" + :title-link-class="`js-${scope}-tab-${tab.scope} gl-display-inline-flex`" + :title-link-attributes="{ 'data-testid': `${scope}-tab-${tab.scope}` }" + :active="tab.isActive" + @click="onTabClick(tab)" > - <a - :class="`js-${scope}-tab-${tab.scope}`" - :data-testid="`${scope}-tab-${tab.scope}`" - role="button" - @click="onTabClick(tab)" - > - {{ tab.name }} - - <span v-if="shouldRenderBadge(tab.count)" class="badge badge-pill"> {{ tab.count }} </span> - </a> - </li> - </ul> + <template #title> + <span class="gl-mr-2"> {{ tab.name }} </span> + <gl-badge v-if="shouldRenderBadge(tab.count)" size="sm" class="gl-tab-counter-badge">{{ + tab.count + }}</gl-badge> + </template> + </gl-tab> + </gl-tabs> </template> diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue index f30676e8ef3..cc1203f83f0 100644 --- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue @@ -78,16 +78,10 @@ export default { }, // following 2 methods taken from code in `collapseLongCommitList` of notes.js: actionTextHtml() { - return $(this.note.note_html) - .unwrap() - .html(); + return $(this.note.note_html).unwrap().html(); }, hasMoreCommits() { - return ( - $(this.note.note_html) - .filter('ul') - .children().length > MAX_VISIBLE_COMMIT_LIST_COUNT - ); + return $(this.note.note_html).filter('ul').children().length > MAX_VISIBLE_COMMIT_LIST_COUNT; }, descriptionVersion() { return this.descriptionVersions[this.note.description_version_id]; diff --git a/app/assets/javascripts/vue_shared/components/ordered_layout.vue b/app/assets/javascripts/vue_shared/components/ordered_layout.vue index 117e79ca39f..a57ff10de71 100644 --- a/app/assets/javascripts/vue_shared/components/ordered_layout.vue +++ b/app/assets/javascripts/vue_shared/components/ordered_layout.vue @@ -4,7 +4,7 @@ export default { render(h, context) { const { slotKeys } = context.props; const slots = context.slots(); - const children = slotKeys.map(key => slots[key]).filter(x => x); + const children = slotKeys.map((key) => slots[key]).filter((x) => x); return children; }, diff --git a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue index 1fc39c7cb8e..d03987bbbe0 100644 --- a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue +++ b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs.vue @@ -203,7 +203,7 @@ export default { this.resetPagination(); const filterParams = { authorUsername: '', assigneeUsername: '', search: '' }; - filters.forEach(filter => { + filters.forEach((filter) => { if (typeof filter === 'object') { switch (filter.type) { case 'author_username': diff --git a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/utils.js b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/utils.js index 7de4263acbb..7855c7ea6cb 100644 --- a/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/utils.js +++ b/app/assets/javascripts/vue_shared/components/paginated_table_with_search_and_tabs/utils.js @@ -6,6 +6,6 @@ import { __ } from '~/locale'; * @param {String} value * @returns {String} */ -export const isAny = value => { +export const isAny = (value) => { return value === __('Any') ? '' : value; }; diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue index 85481f3f7b4..3c0ac32e512 100644 --- a/app/assets/javascripts/vue_shared/components/pikaday.vue +++ b/app/assets/javascripts/vue_shared/components/pikaday.vue @@ -1,20 +1,13 @@ <script> -import Pikaday from 'pikaday'; -import { GlIcon } from '@gitlab/ui'; -import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility'; -import { __ } from '~/locale'; +import { GlDatepicker } from '@gitlab/ui'; +import { pikadayToString } from '~/lib/utils/datetime_utility'; export default { name: 'DatePicker', components: { - GlIcon, + GlDatepicker, }, props: { - label: { - type: String, - required: false, - default: __('Date picker'), - }, selectedDate: { type: Date, required: false, @@ -31,32 +24,9 @@ export default { default: null, }, }, - mounted() { - this.calendar = new Pikaday({ - field: this.$el.querySelector('.dropdown-menu-toggle'), - theme: 'gitlab-theme animate-picker', - format: 'yyyy-mm-dd', - container: this.$el, - defaultDate: this.selectedDate, - setDefaultDate: Boolean(this.selectedDate), - minDate: this.minDate, - maxDate: this.maxDate, - parse: dateString => parsePikadayDate(dateString), - toString: date => pikadayToString(date), - onSelect: this.selected.bind(this), - onClose: this.toggled.bind(this), - firstDay: gon.first_day_of_week, - }); - - this.$el.append(this.calendar.el); - this.calendar.show(); - }, - beforeDestroy() { - this.calendar.destroy(); - }, methods: { - selected(dateText) { - this.$emit('newDateSelected', this.calendar.toString(dateText)); + selected(date) { + this.$emit('newDateSelected', pikadayToString(date)); }, toggled() { this.$emit('hidePicker'); @@ -66,12 +36,13 @@ export default { </script> <template> - <div class="pikaday-container"> - <div class="dropdown open"> - <button type="button" class="dropdown-menu-toggle" data-toggle="dropdown" @click="toggled"> - <span class="dropdown-toggle-text"> {{ label }} </span> - <gl-icon name="chevron-down" class="gl-absolute gl-right-3 gl-top-3 gl-text-gray-500" /> - </button> - </div> - </div> + <gl-datepicker + :value="selectedDate" + :min-date="minDate" + :max-date="maxDate" + start-opened + @close="toggled" + @click="toggled" + @input="selected" + /> </template> diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue index 154671fe9fa..65bd4e4382d 100644 --- a/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue +++ b/app/assets/javascripts/vue_shared/components/project_selector/project_list_item.vue @@ -13,7 +13,7 @@ export default { project: { type: Object, required: true, - validator: p => + validator: (p) => (Number.isFinite(p.id) || isString(p.id)) && isString(p.name) && (isString(p.name_with_namespace) || isString(p.nameWithNamespace)), diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue index 4e2029cd74f..e659e2155fb 100644 --- a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue +++ b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue @@ -48,10 +48,14 @@ export default { data() { return { searchQuery: '', + hasSearched: false, }; }, computed: { legendText() { + if (!this.hasSearched) { + return ''; + } const count = this.projectSearchResults.length; const total = this.totalResults; @@ -75,6 +79,9 @@ export default { return this.selectedProjects.some(({ id }) => project.id === id); }, onInput: debounce(function debouncedOnInput() { + if (!this.hasSearched) { + this.hasSearched = true; + } this.$emit('searched', this.searchQuery); }, SEARCH_INPUT_TIMEOUT_MS), }, @@ -115,7 +122,7 @@ export default { </template> <template #default> - {{ legendText }} + <span data-testid="legend-text">{{ legendText }}</span> </template> </gl-infinite-scroll> <div v-if="showNoResultsMessage" class="text-muted ml-2 js-no-results-message"> diff --git a/app/assets/javascripts/vue_shared/components/registry/list_item.vue b/app/assets/javascripts/vue_shared/components/registry/list_item.vue index 7046ac5be03..8965dba3e83 100644 --- a/app/assets/javascripts/vue_shared/components/registry/list_item.vue +++ b/app/assets/javascripts/vue_shared/components/registry/list_item.vue @@ -39,7 +39,7 @@ export default { }, }, mounted() { - this.detailsSlots = Object.keys(this.$slots).filter(k => k.startsWith('details-')); + this.detailsSlots = Object.keys(this.$slots).filter((k) => k.startsWith('details-')); }, methods: { toggleDetails() { diff --git a/app/assets/javascripts/vue_shared/components/registry/metadata_item.vue b/app/assets/javascripts/vue_shared/components/registry/metadata_item.vue index 8ef623b68eb..93396219a54 100644 --- a/app/assets/javascripts/vue_shared/components/registry/metadata_item.vue +++ b/app/assets/javascripts/vue_shared/components/registry/metadata_item.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon, GlLink } from '@gitlab/ui'; +import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; export default { @@ -9,6 +9,9 @@ export default { GlLink, TooltipOnTruncate, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { icon: { type: String, @@ -32,6 +35,11 @@ export default { return !value || ['xs', 's', 'm', 'l', 'xl'].includes(value); }, }, + textTooltip: { + type: String, + required: false, + default: '', + }, }, computed: { sizeClass() { @@ -55,9 +63,12 @@ export default { class="gl-font-weight-bold gl-display-inline-flex" :class="sizeClass" > - <tooltip-on-truncate :title="text" class="gl-text-truncate"> + <tooltip-on-truncate v-if="!textTooltip" :title="text" class="gl-text-truncate"> {{ text }} </tooltip-on-truncate> + <span v-else v-gl-tooltip="{ title: textTooltip }" data-testid="text-tooltip-container"> + {{ text }}</span + > </div> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/registry/title_area.vue b/app/assets/javascripts/vue_shared/components/registry/title_area.vue index 4d47a34c9a3..c63d91b78d3 100644 --- a/app/assets/javascripts/vue_shared/components/registry/title_area.vue +++ b/app/assets/javascripts/vue_shared/components/registry/title_area.vue @@ -1,5 +1,5 @@ <script> -import { GlAvatar, GlSprintf, GlLink } from '@gitlab/ui'; +import { GlAvatar, GlSprintf, GlLink, GlSkeletonLoader } from '@gitlab/ui'; export default { name: 'TitleArea', @@ -7,6 +7,7 @@ export default { GlAvatar, GlSprintf, GlLink, + GlSkeletonLoader, }, props: { avatar: { @@ -24,6 +25,11 @@ export default { default: () => [], required: false, }, + metadataLoading: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -32,11 +38,11 @@ export default { }, async mounted() { const METADATA_PREFIX = 'metadata-'; - this.metadataSlots = Object.keys(this.$slots).filter(k => k.startsWith(METADATA_PREFIX)); + this.metadataSlots = Object.keys(this.$slots).filter((k) => k.startsWith(METADATA_PREFIX)); // we need to wait for next tick to ensure that dynamic names slots are picked up await this.$nextTick(); - this.metadataSlots = Object.keys(this.$slots).filter(k => k.startsWith(METADATA_PREFIX)); + this.metadataSlots = Object.keys(this.$slots).filter((k) => k.startsWith(METADATA_PREFIX)); }, }; </script> @@ -44,7 +50,7 @@ export default { <template> <div class="gl-display-flex gl-flex-direction-column"> <div class="gl-display-flex gl-justify-content-space-between gl-py-3"> - <div class="gl-flex-direction-column"> + <div class="gl-flex-direction-column gl-flex-grow-1"> <div class="gl-display-flex"> <gl-avatar v-if="avatar" @@ -68,13 +74,23 @@ export default { </div> <div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-mt-3"> - <div - v-for="(row, metadataIndex) in metadataSlots" - :key="metadataIndex" - class="gl-display-flex gl-align-items-center gl-mr-5" - > - <slot :name="row"></slot> - </div> + <template v-if="!metadataLoading"> + <div + v-for="(row, metadataIndex) in metadataSlots" + :key="metadataIndex" + class="gl-display-flex gl-align-items-center gl-mr-5" + > + <slot :name="row"></slot> + </div> + </template> + <template v-else> + <div class="gl-w-full"> + <gl-skeleton-loader :width="960" :height="16" preserve-aspect-ratio="xMinYMax meet"> + <circle cx="6" cy="8" r="6" /> + <rect x="16" y="4" width="200" height="8" rx="4" /> + </gl-skeleton-loader> + </div> + </template> </div> </div> <div v-if="$slots['right-actions']" class="gl-mt-3"> @@ -89,7 +105,7 @@ export default { data-testid="info-message" > <gl-sprintf :message="message.text"> - <template #docLink="{content}"> + <template #docLink="{ content }"> <gl-link :href="message.link" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue index fe50a459e52..5d4c192c78f 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/rich_content_editor.vue @@ -20,7 +20,7 @@ export default { components: { ToastEditor: () => import(/* webpackChunkName: 'toast_editor' */ '@toast-ui/vue-editor').then( - toast => toast.Editor, + (toast) => toast.Editor, ), AddImageModal, InsertVideoModal, diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js index 108c60c3edb..624b5b09b38 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_custom_renderer.js @@ -17,12 +17,12 @@ const listItemRenderers = [renderListItem]; const softbreakRenderers = [renderSoftbreak]; const executeRenderer = (renderers, node, context) => { - const availableRenderer = renderers.find(renderer => renderer.canRender(node, context)); + const availableRenderer = renderers.find((renderer) => renderer.canRender(node, context)); return availableRenderer ? availableRenderer.render(node, context) : context.origin(); }; -const buildCustomHTMLRenderer = customRenderers => { +const buildCustomHTMLRenderer = (customRenderers) => { const renderersByType = { ...customRenderers, htmlBlock: union(htmlBlockRenderers, customRenderers?.htmlBlock), @@ -34,7 +34,7 @@ const buildCustomHTMLRenderer = customRenderers => { softbreak: union(softbreakRenderers, customRenderers?.softbreak), }; - return mapValues(renderersByType, renderers => { + return mapValues(renderersByType, (renderers) => { return (node, context) => executeRenderer(renderers, node, context); }); }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js index 9744e25a8e1..273e0a59963 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/build_html_to_markdown_renderer.js @@ -9,7 +9,7 @@ const DEFAULTS = { emphasis: '_', }; -const countIndentSpaces = text => { +const countIndentSpaces = (text) => { const matches = text.match(/^\s+/m); return matches ? matches[0].length : 0; @@ -52,7 +52,7 @@ const buildHTMLToMarkdownRender = (baseRenderer, formattingPreferences = {}) => const firstLevelIndentSpacesCount = countIndentSpaces(baseResult) || 1; const reindentedList = baseResult .split('\n') - .map(line => { + .map((line) => { const itemIndentSpacesCount = countIndentSpaces(line); const nestingLevel = Math.ceil(itemIndentSpacesCount / firstLevelIndentSpacesCount); const indentSpaces = repeat(' ', subListIndentSpaces * nestingLevel); diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js index 463e64b4936..be78651d38d 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/editor_service.js @@ -6,7 +6,7 @@ import buildCustomHTMLRenderer from './build_custom_renderer'; import { TOOLBAR_ITEM_CONFIGS, VIDEO_ATTRIBUTES } from '../constants'; import sanitizeHTML from './sanitize_html'; -const buildWrapper = propsData => { +const buildWrapper = (propsData) => { const instance = new Vue({ render(createElement) { return createElement(ToolbarItem, propsData); @@ -17,7 +17,7 @@ const buildWrapper = propsData => { return instance.$el; }; -const buildVideoIframe = src => { +const buildVideoIframe = (src) => { const wrapper = document.createElement('figure'); const iframe = document.createElement('iframe'); const videoAttributes = { ...VIDEO_ATTRIBUTES, src }; @@ -48,7 +48,7 @@ const buildImg = (alt, originalSrc, file) => { return img; }; -export const generateToolbarItem = config => { +export const generateToolbarItem = (config) => { const { icon, classes, event, command, tooltip, isDivider } = config; if (isDivider) { @@ -92,14 +92,14 @@ export const insertVideo = ({ editor }, url) => { } }; -export const getMarkdown = editorInstance => editorInstance.invoke('getMarkdown'); +export const getMarkdown = (editorInstance) => editorInstance.invoke('getMarkdown'); /** * This function allow us to extend Toast UI HTML to Markdown renderer. It is * a temporary measure because Toast UI does not provide an API * to achieve this goal. */ -export const registerHTMLToMarkdownRenderer = editorApi => { +export const registerHTMLToMarkdownRenderer = (editorApi) => { const { renderer } = editorApi.toMarkOptions; Object.assign(editorApi.toMarkOptions, { @@ -107,10 +107,10 @@ export const registerHTMLToMarkdownRenderer = editorApi => { }); }; -export const getEditorOptions = externalOptions => { +export const getEditorOptions = (externalOptions) => { return defaults({ customHTMLRenderer: buildCustomHTMLRenderer(externalOptions?.customRenderers), - toolbarItems: TOOLBAR_ITEM_CONFIGS.map(toolbarItem => generateToolbarItem(toolbarItem)), - customHTMLSanitizer: html => sanitizeHTML(html), + toolbarItems: TOOLBAR_ITEM_CONFIGS.map((toolbarItem) => generateToolbarItem(toolbarItem)), + customHTMLSanitizer: (html) => sanitizeHTML(html), }); }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js index 1dcecd5fb8c..638e5fd6f60 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token.js @@ -32,20 +32,20 @@ export const buildUneditableCloseTokens = (token, tagType = TAG_TYPES.block) => // Complete helpers (open plus close) -export const buildTextToken = content => buildToken('text', null, { content }); +export const buildTextToken = (content) => buildToken('text', null, { content }); -export const buildUneditableBlockTokens = token => { +export const buildUneditableBlockTokens = (token) => { return [...buildUneditableOpenTokens(token), buildUneditableCloseToken()]; }; -export const buildUneditableInlineTokens = token => { +export const buildUneditableInlineTokens = (token) => { return [ ...buildUneditableOpenTokens(token, TAG_TYPES.inline), buildUneditableCloseToken(TAG_TYPES.inline), ]; }; -export const buildUneditableHtmlAsTextTokens = node => { +export const buildUneditableHtmlAsTextTokens = (node) => { /* Toast UI internally appends ' data-tomark-pass ' attribute flags so it can target certain nested nodes for internal use during Markdown <=> WYSIWYG conversions. In our case, we want diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js index 18bd17d43d9..30012c1123f 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_html_block.js @@ -2,7 +2,7 @@ import { buildUneditableHtmlAsTextTokens } from './build_uneditable_token'; import { ALLOWED_VIDEO_ORIGINS } from '../../constants'; import { getURLOrigin } from '~/lib/utils/url_utility'; -const isVideoFrame = html => { +const isVideoFrame = (html) => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const { @@ -18,6 +18,6 @@ const canRender = ({ type, literal }) => { return type === 'htmlBlock' && !isVideoFrame(literal); }; -const render = node => buildUneditableHtmlAsTextTokens(node); +const render = (node) => buildUneditableHtmlAsTextTokens(node); export default { canRender, render }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js index a9c3dfcd728..d7716543b53 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_instance_text.js @@ -17,7 +17,7 @@ Regexp notes: */ const identifierInstanceRegex = /((?:\[.+?\]){1}(?:\[\]|\[.+?\])?(?!:))/g; -const isIdentifierInstance = literal => { +const isIdentifierInstance = (literal) => { // Reset lastIndex as global flag in regexp are stateful (https://stackoverflow.com/a/11477448) identifierInstanceRegex.lastIndex = 0; return identifierInstanceRegex.test(literal); @@ -25,9 +25,9 @@ const isIdentifierInstance = literal => { const canRender = ({ literal }) => isIdentifierInstance(literal); -const tokenize = text => { +const tokenize = (text) => { const matches = text.split(identifierInstanceRegex); - const tokens = matches.map(match => { + const tokens = matches.map((match) => { const token = buildTextToken(match); return isIdentifierInstance(match) ? buildUneditableInlineTokens(token) : token; }); diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js index 3f9c6291d1b..4829f0f2243 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_identifier_paragraph.js @@ -1,6 +1,6 @@ const identifierRegex = /(^\[.+\]: .+)/; -const isIdentifier = text => { +const isIdentifier = (text) => { return identifierRegex.test(text); }; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_softbreak.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_softbreak.js index 389ade5f27a..c004e839821 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_softbreak.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_softbreak.js @@ -1,4 +1,4 @@ -const canRender = node => ['emph', 'strong'].includes(node.parent?.type); +const canRender = (node) => ['emph', 'strong'].includes(node.parent?.type); const render = () => ({ type: 'text', content: ' ', diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js index 4cba2c70486..eff5dbf59f2 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/renderers/render_utils.js @@ -11,9 +11,9 @@ export const renderUneditableBranch = (_, { entering, origin }) => const attributeDefinitionRegexp = /(^{:.+}$)/; -export const isAttributeDefinition = text => attributeDefinitionRegexp.test(text); +export const isAttributeDefinition = (text) => attributeDefinitionRegexp.test(text); -const findAttributeDefinition = node => { +const findAttributeDefinition = (node) => { const literal = node?.next?.firstChild?.literal || node?.firstChild?.firstChild?.next?.next?.literal; // for headings // for list items; diff --git a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/sanitize_html.js b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/sanitize_html.js index eae2e0335c1..cb0f1d51cb1 100644 --- a/app/assets/javascripts/vue_shared/components/rich_content_editor/services/sanitize_html.js +++ b/app/assets/javascripts/vue_shared/components/rich_content_editor/services/sanitize_html.js @@ -5,7 +5,7 @@ import { getURLOrigin } from '~/lib/utils/url_utility'; const sanitizer = createSanitizer(window); const ADD_TAGS = ['iframe']; -sanitizer.addHook('uponSanitizeElement', node => { +sanitizer.addHook('uponSanitizeElement', (node) => { if (node.tagName !== 'IFRAME') { return; } @@ -17,6 +17,6 @@ sanitizer.addHook('uponSanitizeElement', node => { } }); -const sanitize = content => sanitizer.sanitize(content, { ADD_TAGS }); +const sanitize = (content) => sanitizer.sanitize(content, { ADD_TAGS }); export default sanitize; diff --git a/app/assets/javascripts/vue_shared/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue index 3dbf0ccdfa9..6574a5ddfde 100644 --- a/app/assets/javascripts/vue_shared/components/select2_select.vue +++ b/app/assets/javascripts/vue_shared/components/select2_select.vue @@ -26,7 +26,7 @@ export default { $(this.$refs.dropdownInput) .val(this.value) .select2(this.options) - .on('change', event => this.$emit('input', event.target.value)); + .on('change', (event) => this.$emit('input', event.target.value)); }) .catch(() => {}); }, diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue index 1ef3d5627ae..22d86ee25d1 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue @@ -143,7 +143,7 @@ export default { > <slot></slot> </dropdown-value> - <div v-if="canEdit" class="selectbox js-selectbox" style="display: none;"> + <div v-if="canEdit" class="selectbox js-selectbox" style="display: none"> <dropdown-hidden-input v-for="label in context.labels" :key="label.id" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue index 434aabc3df9..795f16f4efc 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue @@ -18,7 +18,7 @@ export default { }, created() { const rawLabelsColors = gon.suggested_label_colors; - this.suggestedColors = Object.keys(rawLabelsColors).map(colorCode => ({ + this.suggestedColors = Object.keys(rawLabelsColors).map((colorCode) => ({ colorCode, title: rawLabelsColors[colorCode], })); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue index 973cc314ee3..122250d1ce7 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue @@ -20,7 +20,7 @@ export default { const labelsString = this.labels.length ? this.labels .slice(0, 5) - .map(label => label.title) + .map((label) => label.title) .join(', ') : s__('LabelSelect|Labels'); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue index 55e2fb68275..41308e352e3 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view.vue @@ -25,7 +25,7 @@ export default { }, suggestedColors() { const colorsMap = gon.suggested_label_colors; - return Object.keys(colorsMap).map(color => ({ [color]: colorsMap[color] })); + return Object.keys(colorsMap).map((color) => ({ [color]: colorsMap[color] })); }, }, methods: { diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue index 8ce624aa303..683889b8611 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue @@ -182,9 +182,9 @@ export default { !state.showDropdownButton && !state.showDropdownContents ) { - let filterFn = label => label.touched; + let filterFn = (label) => label.touched; if (this.isDropdownVariantEmbedded) { - filterFn = label => label.set; + filterFn = (label) => label.set; } this.handleDropdownClose(state.labels.filter(filterFn)); } @@ -204,13 +204,13 @@ export default { 'js-btn-cancel-create', 'js-sidebar-dropdown-toggle', ].some( - className => + (className) => target?.classList.contains(className) || target?.parentElement?.classList.contains(className), ); const hadExceptionParent = ['.js-btn-back', '.js-labels-list'].some( - className => $(target).parents(className).length, + (className) => $(target).parents(className).length, ); if ( diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js index 5a30e29cad3..d14f96720b7 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js @@ -9,7 +9,7 @@ import { DropdownVariant } from '../constants'; */ export const dropdownButtonText = (state, getters) => { const selectedLabels = getters.isDropdownVariantSidebar - ? state.labels.filter(label => label.set) + ? state.labels.filter((label) => label.set) : state.selectedLabels; if (!selectedLabels.length) { @@ -28,25 +28,25 @@ export const dropdownButtonText = (state, getters) => { * selectedLabels array. * @param {object} state */ -export const selectedLabelsList = state => state.selectedLabels.map(label => label.id); +export const selectedLabelsList = (state) => state.selectedLabels.map((label) => label.id); /** * Returns boolean representing whether dropdown variant * is `sidebar` * @param {object} state */ -export const isDropdownVariantSidebar = state => state.variant === DropdownVariant.Sidebar; +export const isDropdownVariantSidebar = (state) => state.variant === DropdownVariant.Sidebar; /** * Returns boolean representing whether dropdown variant * is `standalone` * @param {object} state */ -export const isDropdownVariantStandalone = state => state.variant === DropdownVariant.Standalone; +export const isDropdownVariantStandalone = (state) => state.variant === DropdownVariant.Standalone; /** * Returns boolean representing whether dropdown variant * is `embedded` * @param {object} state */ -export const isDropdownVariantEmbedded = state => state.variant === DropdownVariant.Embedded; +export const isDropdownVariantEmbedded = (state) => state.variant === DropdownVariant.Embedded; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js index 54f8c78b4e1..6de436ffd13 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/mutations.js @@ -33,7 +33,7 @@ export default { // Iterate over every label and add a `set` prop // to determine whether it is already a part of // selectedLabels array. - const selectedLabelIds = state.selectedLabels.map(label => label.id); + const selectedLabelIds = state.selectedLabels.map((label) => label.id); state.labelsFetchInProgress = false; state.labels = labels.reduce((allLabels, label) => { allLabels.push({ @@ -61,7 +61,7 @@ export default { // Find the label to update from all the labels // and change `set` prop value to represent their current state. const labelId = labels.pop()?.id; - const candidateLabel = state.labels.find(label => labelId === label.id); + const candidateLabel = state.labels.find((label) => labelId === label.id); if (candidateLabel) { candidateLabel.touched = true; candidateLabel.set = !candidateLabel.set; diff --git a/app/assets/javascripts/vue_shared/components/split_button.vue b/app/assets/javascripts/vue_shared/components/split_button.vue index 11049028ff6..61b317d0d1d 100644 --- a/app/assets/javascripts/vue_shared/components/split_button.vue +++ b/app/assets/javascripts/vue_shared/components/split_button.vue @@ -2,7 +2,7 @@ import { isString } from 'lodash'; import { GlDropdown, GlDropdownDivider, GlDropdownItem } from '@gitlab/ui'; -const isValidItem = item => +const isValidItem = (item) => isString(item.eventName) && isString(item.title) && isString(item.description); export default { diff --git a/app/assets/javascripts/vue_shared/components/tabs/tabs.js b/app/assets/javascripts/vue_shared/components/tabs/tabs.js index 9b9e4bb47bd..233df96a520 100644 --- a/app/assets/javascripts/vue_shared/components/tabs/tabs.js +++ b/app/assets/javascripts/vue_shared/components/tabs/tabs.js @@ -17,8 +17,8 @@ export default { }, methods: { updateTabs() { - this.tabs = this.$children.filter(child => child.isTab); - this.currentIndex = this.tabs.findIndex(tab => tab.localActive); + this.tabs = this.$children.filter((child) => child.isTab); + this.currentIndex = this.tabs.findIndex((tab) => tab.localActive); }, setTab(e, index) { if (this.stopPropagation) { @@ -48,7 +48,7 @@ export default { href: '#', }, on: { - click: e => this.setTab(e, i), + click: (e) => this.setTab(e, i), }, }, tab.$slots.title || tab.title, diff --git a/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue b/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue index 3fa8efcd145..f1db26ff4fc 100644 --- a/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/timezone_dropdown.vue @@ -36,14 +36,14 @@ export default { }, computed: { timezones() { - return this.timezoneData.map(timezone => ({ + return this.timezoneData.map((timezone) => ({ formattedTimezone: this.formatTimezone(timezone), identifier: timezone.identifier, })); }, filteredResults() { const lowerCasedSearchTerm = this.searchTerm.toLowerCase(); - return this.timezones.filter(timezone => + return this.timezones.filter((timezone) => timezone.formattedTimezone.toLowerCase().includes(lowerCasedSearchTerm), ); }, diff --git a/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue index b645758d891..01ba2cf5c39 100644 --- a/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue +++ b/app/assets/javascripts/vue_shared/components/upload_dropzone/upload_dropzone.vue @@ -62,7 +62,9 @@ export default { return files.every(this.isFileValid); }, isValidDragDataType({ dataTransfer }) { - return Boolean(dataTransfer && dataTransfer.types.some(t => t === VALID_DATA_TRANSFER_TYPE)); + return Boolean( + dataTransfer && dataTransfer.types.some((t) => t === VALID_DATA_TRANSFER_TYPE), + ); }, ondrop({ dataTransfer = {} }) { this.dragCounter = 0; diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue index ea483416c46..efb99eb0d94 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_image.vue @@ -104,7 +104,7 @@ export default { :target="() => $refs.userAvatarImage" :placement="tooltipPlacement" boundary="window" - class="js-user-avatar-image-toolip" + class="js-user-avatar-image-tooltip" > <slot> {{ tooltipText }} </slot> </gl-tooltip> diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue index dbb1a075e76..c957876f8ab 100644 --- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue +++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue @@ -72,7 +72,7 @@ export default { }, computed: { actions() { - return [this.webIdeAction, this.editAction, this.gitpodAction].filter(action => action); + return [this.webIdeAction, this.editAction, this.gitpodAction].filter((action) => action); }, editAction() { if (!this.showEditButton) { diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js index 5511145fba2..5262a15136b 100644 --- a/app/assets/javascripts/vue_shared/constants.js +++ b/app/assets/javascripts/vue_shared/constants.js @@ -54,5 +54,6 @@ export const timeRanges = [ }, ]; -export const defaultTimeRange = timeRanges.find(tr => tr.default); -export const getTimeWindow = timeWindowName => timeRanges.find(tr => tr.name === timeWindowName); +export const defaultTimeRange = timeRanges.find((tr) => tr.default); +export const getTimeWindow = (timeWindowName) => + timeRanges.find((tr) => tr.name === timeWindowName); diff --git a/app/assets/javascripts/vue_shared/directives/autofocusonshow.js b/app/assets/javascripts/vue_shared/directives/autofocusonshow.js index 4659ec20ceb..1be4612a7e4 100644 --- a/app/assets/javascripts/vue_shared/directives/autofocusonshow.js +++ b/app/assets/javascripts/vue_shared/directives/autofocusonshow.js @@ -11,8 +11,8 @@ export default { inserted(el) { if ('IntersectionObserver' in window) { // Element visibility is dynamic, so we attach observer - el.visibilityObserver = new IntersectionObserver(entries => { - entries.forEach(entry => { + el.visibilityObserver = new IntersectionObserver((entries) => { + entries.forEach((entry) => { // Combining `intersectionRatio > 0` and // element's `offsetParent` presence will // deteremine if element is truely visible diff --git a/app/assets/javascripts/vue_shared/directives/validation.js b/app/assets/javascripts/vue_shared/directives/validation.js index 09bec78edcc..ece09df272c 100644 --- a/app/assets/javascripts/vue_shared/directives/validation.js +++ b/app/assets/javascripts/vue_shared/directives/validation.js @@ -12,19 +12,19 @@ import { s__ } from '~/locale'; */ const defaultFeedbackMap = { valueMissing: { - isInvalid: el => el.validity?.valueMissing, + isInvalid: (el) => el.validity?.valueMissing, message: s__('Please fill out this field.'), }, urlTypeMismatch: { - isInvalid: el => el.type === 'url' && el.validity?.typeMismatch, + isInvalid: (el) => el.type === 'url' && el.validity?.typeMismatch, message: s__('Please enter a valid URL format, ex: http://www.example.com/home'), }, }; const getFeedbackForElement = (feedbackMap, el) => - Object.values(feedbackMap).find(f => f.isInvalid(el))?.message || el.validationMessage; + Object.values(feedbackMap).find((f) => f.isInvalid(el))?.message || el.validationMessage; -const focusFirstInvalidInput = e => { +const focusFirstInvalidInput = (e) => { const { target: formEl } = e; const invalidInput = formEl.querySelector('input:invalid'); @@ -33,7 +33,7 @@ const focusFirstInvalidInput = e => { } }; -const isEveryFieldValid = form => Object.values(form.fields).every(({ state }) => state === true); +const isEveryFieldValid = (form) => Object.values(form.fields).every(({ state }) => state === true); const createValidator = (context, feedbackMap) => ({ el, reportInvalidInput = false }) => { const { form } = context; @@ -86,7 +86,7 @@ const createValidator = (context, feedbackMap) => ({ el, reportInvalidInput = fa * @param {Object<string, { message: string, isValid: ?function}>} customFeedbackMap * @returns {{ inserted: function, update: function }} validateDirective */ -export default function(customFeedbackMap = {}) { +export default function (customFeedbackMap = {}) { const feedbackMap = merge(defaultFeedbackMap, customFeedbackMap); const elDataMap = new WeakMap(); diff --git a/app/assets/javascripts/vue_shared/gl_feature_flags_plugin.js b/app/assets/javascripts/vue_shared/gl_feature_flags_plugin.js index 3488a44bd0f..e1734809bce 100644 --- a/app/assets/javascripts/vue_shared/gl_feature_flags_plugin.js +++ b/app/assets/javascripts/vue_shared/gl_feature_flags_plugin.js @@ -1,4 +1,4 @@ -export default Vue => { +export default (Vue) => { Vue.mixin({ provide: { glFeatures: { ...((window.gon && window.gon.features) || {}) }, diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js index cdbde55901d..be04ff158e7 100644 --- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js +++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js @@ -48,7 +48,7 @@ export default { this.poll.stop(); const queryString = Object.keys(parameters) - .map(parameter => { + .map((parameter) => { const value = parameters[parameter]; // update internal state for UI this[parameter] = value; diff --git a/app/assets/javascripts/vue_shared/security_reports/components/security_summary.vue b/app/assets/javascripts/vue_shared/security_reports/components/security_summary.vue index babb9fddcf6..e3aa25a294e 100644 --- a/app/assets/javascripts/vue_shared/security_reports/components/security_summary.vue +++ b/app/assets/javascripts/vue_shared/security_reports/components/security_summary.vue @@ -34,13 +34,13 @@ export default { <template> <span> <gl-sprintf :message="message.message"> - <template #total="{content}"> + <template #total="{ content }"> <strong>{{ content }}</strong> </template> </gl-sprintf> <span v-if="shouldShowCountMessage" class="gl-font-sm"> <gl-sprintf :message="message.countMessage"> - <template v-for="slotName in $options.slotNames" #[slotName]="{content}"> + <template v-for="slotName in $options.slotNames" #[slotName]="{ content }"> <span :key="slotName"> <strong v-if="message[slotName] > 0" diff --git a/app/assets/javascripts/vue_shared/security_reports/constants.js b/app/assets/javascripts/vue_shared/security_reports/constants.js index 68241a8c5be..dd591f7bba3 100644 --- a/app/assets/javascripts/vue_shared/security_reports/constants.js +++ b/app/assets/javascripts/vue_shared/security_reports/constants.js @@ -5,6 +5,15 @@ export const FEEDBACK_TYPE_ISSUE = 'issue'; export const FEEDBACK_TYPE_MERGE_REQUEST = 'merge_request'; /** + * Security artifact file types + */ +export const REPORT_FILE_TYPES = { + ARCHIVE: 'ARCHIVE', + TRACE: 'TRACE', + METADATA: 'METADATA', +}; + +/** * Security scan report types, as provided by the backend. */ export const REPORT_TYPE_SAST = 'sast'; diff --git a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue index bdbf9957ad4..a6c7b59aa71 100644 --- a/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue +++ b/app/assets/javascripts/vue_shared/security_reports/security_reports_app.vue @@ -97,7 +97,7 @@ export default { projectPath: this.targetProjectFullPath, iid: String(this.mrIid), reportTypes: this.$options.reportTypes.map( - reportType => reportTypeToSecurityReportTypeEnum[reportType], + (reportType) => reportTypeToSecurityReportTypeEnum[reportType], ), }; }, @@ -151,7 +151,7 @@ export default { created() { if (!this.canShowDownloads) { this.checkAvailableSecurityReports(this.$options.reportTypes) - .then(availableSecurityReports => { + .then((availableSecurityReports) => { this.onCheckingAvailableSecurityReports(Array.from(availableSecurityReports)); }) .catch(this.showError); diff --git a/app/assets/javascripts/vue_shared/security_reports/store/getters.js b/app/assets/javascripts/vue_shared/security_reports/store/getters.js index 1e5a60c32fd..443255b0e6a 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/getters.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/getters.js @@ -3,7 +3,7 @@ import { countVulnerabilities, groupedTextBuilder } from './utils'; import { LOADING, ERROR, SUCCESS } from '~/reports/constants'; import { TRANSLATION_IS_LOADING } from './messages'; -export const summaryCounts = state => +export const summaryCounts = (state) => countVulnerabilities( state.reportTypes.reduce((acc, reportType) => { acc.push(...state[reportType].newIssues); @@ -50,17 +50,17 @@ export const summaryStatus = (state, getters) => { return SUCCESS; }; -export const areReportsLoading = state => - state.reportTypes.some(reportType => state[reportType].isLoading); +export const areReportsLoading = (state) => + state.reportTypes.some((reportType) => state[reportType].isLoading); -export const areAllReportsLoading = state => - state.reportTypes.every(reportType => state[reportType].isLoading); +export const areAllReportsLoading = (state) => + state.reportTypes.every((reportType) => state[reportType].isLoading); -export const allReportsHaveError = state => - state.reportTypes.every(reportType => state[reportType].hasError); +export const allReportsHaveError = (state) => + state.reportTypes.every((reportType) => state[reportType].hasError); -export const anyReportHasError = state => - state.reportTypes.some(reportType => state[reportType].hasError); +export const anyReportHasError = (state) => + state.reportTypes.some((reportType) => state[reportType].hasError); -export const anyReportHasIssues = state => - state.reportTypes.some(reportType => state[reportType].newIssues.length > 0); +export const anyReportHasIssues = (state) => + state.reportTypes.some((reportType) => state[reportType].newIssues.length > 0); diff --git a/app/assets/javascripts/vue_shared/security_reports/store/modules/sast/actions.js b/app/assets/javascripts/vue_shared/security_reports/store/modules/sast/actions.js index 22a45341c51..0f26e3c30ef 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/modules/sast/actions.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/modules/sast/actions.js @@ -15,7 +15,7 @@ export const fetchDiff = ({ state, rootState, dispatch }) => { dispatch('requestDiff'); return fetchDiffData(rootState, state.paths.diffEndpoint, 'sast') - .then(data => { + .then((data) => { dispatch('receiveDiffSuccess', data); }) .catch(() => { diff --git a/app/assets/javascripts/vue_shared/security_reports/store/modules/secret_detection/actions.js b/app/assets/javascripts/vue_shared/security_reports/store/modules/secret_detection/actions.js index c9da824613d..e3ae5435f5d 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/modules/secret_detection/actions.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/modules/secret_detection/actions.js @@ -15,7 +15,7 @@ export const fetchDiff = ({ state, rootState, dispatch }) => { dispatch('requestDiff'); return fetchDiffData(rootState, state.paths.diffEndpoint, 'secret_detection') - .then(data => { + .then((data) => { dispatch('receiveDiffSuccess', data); }) .catch(() => { diff --git a/app/assets/javascripts/vue_shared/security_reports/store/utils.js b/app/assets/javascripts/vue_shared/security_reports/store/utils.js index c5e786c92b1..fd6613ae11c 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/utils.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/utils.js @@ -29,7 +29,7 @@ export const fetchDiffData = (state, endpoint, category) => { */ export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) => feedback - .filter(fb => fb.project_fingerprint === vulnerability.project_fingerprint) + .filter((fb) => fb.project_fingerprint === vulnerability.project_fingerprint) .reduce((vuln, fb) => { if (fb.feedback_type === FEEDBACK_TYPE_DISMISSAL) { return { @@ -63,7 +63,7 @@ export const enrichVulnerabilityWithFeedback = (vulnerability, feedback = []) => * @returns {Object} */ export const parseDiff = (diff, enrichData) => { - const enrichVulnerability = vulnerability => ({ + const enrichVulnerability = (vulnerability) => ({ ...enrichVulnerabilityWithFeedback(vulnerability, enrichData), category: vulnerability.report_type, title: vulnerability.message || vulnerability.name, diff --git a/app/assets/javascripts/vue_shared/security_reports/utils.js b/app/assets/javascripts/vue_shared/security_reports/utils.js index 827a87f9aaf..ad819bf7081 100644 --- a/app/assets/javascripts/vue_shared/security_reports/utils.js +++ b/app/assets/javascripts/vue_shared/security_reports/utils.js @@ -1,4 +1,18 @@ -import { securityReportTypeEnumToReportType } from './constants'; +import { capitalize } from 'lodash'; +import { + securityReportTypeEnumToReportType, + REPORT_FILE_TYPES, +} from 'ee_else_ce/vue_shared/security_reports/constants'; + +const addReportTypeIfExists = (acc, reportTypes, reportType, getName, downloadPath) => { + if (reportTypes && reportTypes.includes(reportType)) { + acc.push({ + reportType, + name: getName(reportType), + path: downloadPath, + }); + } +}; export const extractSecurityReportArtifacts = (reportTypes, data) => { const jobs = data.project?.mergeRequest?.headPipeline?.jobs?.nodes ?? []; @@ -7,14 +21,21 @@ export const extractSecurityReportArtifacts = (reportTypes, data) => { const artifacts = job.artifacts?.nodes ?? []; artifacts.forEach(({ downloadPath, fileType }) => { - const reportType = securityReportTypeEnumToReportType[fileType]; - if (reportType && reportTypes.includes(reportType)) { - acc.push({ - name: job.name, - reportType, - path: downloadPath, - }); - } + addReportTypeIfExists( + acc, + reportTypes, + securityReportTypeEnumToReportType[fileType], + () => job.name, + downloadPath, + ); + + addReportTypeIfExists( + acc, + reportTypes, + REPORT_FILE_TYPES[fileType], + (reportType) => `${job.name} ${capitalize(reportType)}`, + downloadPath, + ); }); return acc; diff --git a/app/assets/javascripts/vue_shared/translate.js b/app/assets/javascripts/vue_shared/translate.js index e0baf03acc3..616848639f1 100644 --- a/app/assets/javascripts/vue_shared/translate.js +++ b/app/assets/javascripts/vue_shared/translate.js @@ -1,6 +1,6 @@ import { __, n__, s__, sprintf } from '../locale'; -export default Vue => { +export default (Vue) => { Vue.mixin({ methods: { /** |