diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared')
59 files changed, 634 insertions, 222 deletions
diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue index 948d2505966..c93f620995f 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue @@ -278,7 +278,7 @@ export default { data-testid="viewIncidentBtn" :href="incidentPath(alert.issue.iid)" category="primary" - variant="success" + variant="confirm" > {{ s__('AlertManagement|View incident') }} </gl-button> @@ -288,7 +288,7 @@ export default { data-testid="createIncidentBtn" :loading="incidentCreationInProgress" category="primary" - variant="success" + variant="confirm" @click="createIncident()" > {{ s__('AlertManagement|Create incident') }} diff --git a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue index 8b76af05ffe..6a03e38a31d 100644 --- a/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue +++ b/app/assets/javascripts/vue_shared/components/ci_cd_analytics/ci_cd_analytics_charts.vue @@ -1,12 +1,12 @@ <script> -import { GlSegmentedControl } from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; +import SegmentedControlButtonGroup from '~/vue_shared/components/segmented_control_button_group.vue'; import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue'; export default { components: { - GlSegmentedControl, CiCdAnalyticsAreaChart, + SegmentedControlButtonGroup, }, props: { charts: { @@ -38,7 +38,11 @@ export default { </script> <template> <div> - <gl-segmented-control v-model="selectedChart" :options="chartRanges" class="gl-mb-4" /> + <segmented-control-button-group + v-model="selectedChart" + :options="chartRanges" + class="gl-mb-4" + /> <ci-cd-analytics-area-chart v-if="chart" v-bind="$attrs" diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue index 07bd6019b80..9bccc49e894 100644 --- a/app/assets/javascripts/vue_shared/components/ci_icon.vue +++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue @@ -11,17 +11,22 @@ import { GlIcon } from '@gitlab/ui'; * } * * Used in: - * - Pipelines table Badge - * - Pipelines table mini graph - * - Pipeline graph - * - Pipeline show view badge - * - Jobs table + * - Extended MR Popover * - Jobs show view header * - Jobs show view sidebar + * - Jobs table * - Linked pipelines - * - Extended MR Popover + * - Pipeline graph + * - Pipeline mini graph + * - Pipeline show view badge + * - Pipelines table Badge + */ + +/* + * These sizes are defined in gitlab-ui/src/scss/variables.scss + * under '$gl-icon-sizes' */ -const validSizes = [8, 12, 16, 18, 24, 32, 48, 72]; +const validSizes = [8, 12, 14, 16, 24, 32, 48, 72]; export default { components: { @@ -45,6 +50,11 @@ export default { required: false, default: false, }, + isInteractive: { + type: Boolean, + required: false, + default: false, + }, cssClasses: { type: String, required: false, @@ -52,9 +62,9 @@ export default { }, }, computed: { - cssClass() { + wrapperStyleClasses() { const status = this.status.group; - return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status}`; + return `ci-status-icon ci-status-icon-${status} js-ci-status-icon-${status} gl-rounded-full gl-justify-content-center`; }, icon() { return this.borderless ? `${this.status.icon}_borderless` : this.status.icon; @@ -63,7 +73,10 @@ export default { }; </script> <template> - <span :class="cssClass"> + <span + :class="[wrapperStyleClasses, { interactive: isInteractive }]" + :style="{ height: `${size}px`, width: `${size}px` }" + > <gl-icon :name="icon" :size="size" :class="cssClasses" :aria-label="status.icon" /> </span> </template> diff --git a/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue b/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue index 7a166f9a3e4..78db2bf15b0 100644 --- a/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue +++ b/app/assets/javascripts/vue_shared/components/color_picker/color_picker.vue @@ -4,6 +4,7 @@ * * @example * <color-picker + :id="example-id" :invalid-feedback="__('Please enter a valid hex (#RRGGBB or #RGB) color value')" :label="__('Background color')" :value="#FF0000" @@ -12,6 +13,7 @@ /> */ import { GlFormGroup, GlFormInput, GlFormInputGroup, GlLink, GlTooltipDirective } from '@gitlab/ui'; +import { uniqueId } from 'lodash'; import { __ } from '~/locale'; const PREVIEW_COLOR_DEFAULT_CLASSES = @@ -29,6 +31,11 @@ export default { GlTooltip: GlTooltipDirective, }, props: { + id: { + type: String, + required: false, + default: () => uniqueId('color-picker-'), + }, invalidFeedback: { type: String, required: false, @@ -94,14 +101,13 @@ export default { <div> <gl-form-group :label="label" - label-for="color-picker" + :label-for="id" :description="description" :invalid-feedback="invalidFeedback" :state="state" :class="{ 'gl-mb-3!': hasSuggestedColors }" > <gl-form-input-group - id="color-picker" max-length="7" type="text" class="gl-align-center gl-rounded-0 gl-rounded-top-right-base gl-rounded-bottom-right-base" @@ -112,6 +118,7 @@ export default { <template #prepend> <div :class="previewColorClasses" :style="previewColor" data-testid="color-preview"> <gl-form-input + :id="id" type="color" class="gl-absolute gl-top-0 gl-left-0 gl-h-full! gl-p-0! gl-m-0! gl-opacity-0" tabindex="-1" diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue index ebbc1bfb037..388353bc35b 100644 --- a/app/assets/javascripts/vue_shared/components/commit.vue +++ b/app/assets/javascripts/vue_shared/components/commit.vue @@ -172,7 +172,9 @@ export default { :img-src="author.avatar_url" :img-alt="userImageAltDescription" :tooltip-text="author.username" + :img-size="16" class="avatar-image-container text-decoration-none" + img-css-classes="gl-mr-3" /> <tooltip-on-truncate :title="title" class="flex-truncate-child"> <gl-link :href="commitUrl" class="commit-row-message cgray">{{ title }}</gl-link> diff --git a/app/assets/javascripts/vue_shared/components/confidentiality_badge.vue b/app/assets/javascripts/vue_shared/components/confidentiality_badge.vue new file mode 100644 index 00000000000..298c7bc50cc --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/confidentiality_badge.vue @@ -0,0 +1,39 @@ +<script> +import { GlBadge, GlTooltipDirective } from '@gitlab/ui'; +import { confidentialityInfoText } from '../constants'; + +export default { + components: { + GlBadge, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + workspaceType: { + type: String, + required: true, + }, + issuableType: { + type: String, + required: true, + }, + }, + computed: { + confidentialTooltip() { + return confidentialityInfoText(this.workspaceType, this.issuableType); + }, + }, +}; +</script> + +<template> + <gl-badge + v-gl-tooltip.bottom + :title="confidentialTooltip" + icon="eye-slash" + variant="warning" + class="gl-display-inline gl-mr-2" + >{{ __('Confidential') }}</gl-badge + > +</template> diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js index 6629b293eb9..8481280f25f 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js @@ -14,6 +14,8 @@ const Template = (args, { argTypes }) => ({ additionalInformation: args.additionalInformation || null, confirmDangerMessage: args.confirmDangerMessage || 'You require more Vespene Gas', htmlConfirmationMessage: args.confirmDangerMessage || false, + confirmButtonText: args.confirmButtonText || 'Cancel', + cancelButtonText: args.cancelButtonText || 'Confirm', }, }); diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue index 88890b3332d..37e480f7e41 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue @@ -12,7 +12,7 @@ import { CONFIRM_DANGER_MODAL_TITLE, CONFIRM_DANGER_PHRASE_TEXT, CONFIRM_DANGER_WARNING, - CONFIRM_DANGER_MODAL_ERROR, + CONFIRM_DANGER_MODAL_CANCEL, } from './constants'; export default { @@ -40,6 +40,9 @@ export default { additionalInformation: { default: CONFIRM_DANGER_WARNING, }, + cancelButtonText: { + default: CONFIRM_DANGER_MODAL_CANCEL, + }, }, props: { modalId: { @@ -66,6 +69,11 @@ export default { attributes: [{ variant: 'danger', disabled: !this.isValid, class: 'qa-confirm-button' }], }; }, + actionCancel() { + return { + text: this.cancelButtonText, + }; + }, }, methods: { equalString(a, b) { @@ -77,7 +85,6 @@ export default { CONFIRM_DANGER_MODAL_TITLE, CONFIRM_DANGER_WARNING, CONFIRM_DANGER_PHRASE_TEXT, - CONFIRM_DANGER_MODAL_ERROR, }, }; </script> @@ -88,6 +95,7 @@ export default { :data-testid="modalId" :title="$options.i18n.CONFIRM_DANGER_MODAL_TITLE" :action-primary="actionPrimary" + :action-cancel="actionCancel" @primary="$emit('confirm')" > <gl-alert @@ -110,7 +118,7 @@ export default { </template> </gl-sprintf> </p> - <gl-form-group :state="isValid" :invalid-feedback="$options.i18n.CONFIRM_DANGER_MODAL_ERROR"> + <gl-form-group :state="isValid"> <gl-form-input id="confirm_name_input" v-model="confirmationPhrase" diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js b/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js index fa44a9be411..90d55d0f93f 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/constants.js @@ -2,7 +2,6 @@ import { __ } from '~/locale'; export const CONFIRM_DANGER_MODAL_ID = 'confirm-danger-modal'; export const CONFIRM_DANGER_MODAL_TITLE = __('Confirmation required'); -export const CONFIRM_DANGER_MODAL_ERROR = __('Confirmation required'); export const CONFIRM_DANGER_MODAL_BUTTON = __('Confirm'); export const CONFIRM_DANGER_WARNING = __( 'This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.', @@ -10,3 +9,4 @@ export const CONFIRM_DANGER_WARNING = __( export const CONFIRM_DANGER_PHRASE_TEXT = __( 'Please type %{phrase_code} to proceed or close this modal to cancel.', ); +export const CONFIRM_DANGER_MODAL_CANCEL = __('Cancel'); 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 e546ca57c5e..181c1b89e31 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 @@ -248,7 +248,7 @@ export default { __('Cancel') }}</gl-button> <gl-button - variant="success" + variant="confirm" category="primary" :disabled="!isValid" @click="setFixedRange()" diff --git a/app/assets/javascripts/vue_shared/components/deployment_instance.vue b/app/assets/javascripts/vue_shared/components/deployment_instance.vue index 41b783aa011..4aae86fc82b 100644 --- a/app/assets/javascripts/vue_shared/components/deployment_instance.vue +++ b/app/assets/javascripts/vue_shared/components/deployment_instance.vue @@ -14,6 +14,7 @@ */ import { GlLink, GlTooltipDirective } from '@gitlab/ui'; import { mergeUrlParams } from '~/lib/utils/url_utility'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { components: { @@ -22,7 +23,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - + mixins: [glFeatureFlagsMixin()], props: { /** * Represents the status of the pod. Each state is represented with a different @@ -75,7 +76,9 @@ export default { }, computedLogPath() { - return this.isLink ? mergeUrlParams({ pod_name: this.podName }, this.logsPath) : null; + return this.isLink && this.glFeatures.monitorLogging + ? mergeUrlParams({ pod_name: this.podName }, this.logsPath) + : null; }, }, }; diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_hidden_input.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_hidden_input.vue deleted file mode 100644 index afde0c81580..00000000000 --- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_hidden_input.vue +++ /dev/null @@ -1,18 +0,0 @@ -<script> -export default { - props: { - name: { - type: String, - required: true, - }, - value: { - type: [Number, String], - required: true, - }, - }, -}; -</script> - -<template> - <input :name="name" :value="value" type="hidden" /> -</template> diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue deleted file mode 100644 index edb5ffdc39c..00000000000 --- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_search_input.vue +++ /dev/null @@ -1,49 +0,0 @@ -<script> -import { GlIcon } from '@gitlab/ui'; -import { __ } from '~/locale'; - -export default { - components: { - GlIcon, - }, - props: { - placeholderText: { - type: String, - required: true, - default: __('Search'), - }, - focused: { - type: Boolean, - required: false, - default: false, - }, - }, - data() { - return { searchQuery: this.value }; - }, - watch: { - searchQuery(query) { - this.$emit('input', query); - }, - focused(val) { - if (val) { - this.$refs.searchInput.focus(); - } - }, - }, -}; -</script> - -<template> - <div class="dropdown-input"> - <input - ref="searchInput" - v-model="searchQuery" - :placeholder="placeholderText" - class="dropdown-input-field" - type="search" - autocomplete="off" - /> - <gl-icon name="search" class="dropdown-input-search" data-hidden="true" /> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue index 2a79ccc2648..840911dc99c 100644 --- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue +++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue @@ -1,28 +1,22 @@ <script> import { - GlIcon, GlLoadingIcon, GlDropdown, GlDropdownForm, GlDropdownDivider, GlDropdownItem, - GlDropdownSectionHeader, GlSearchBoxByType, } from '@gitlab/ui'; import { __ } from '~/locale'; -import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; export default { components: { - GlIcon, GlLoadingIcon, GlDropdown, GlDropdownForm, GlDropdownDivider, GlDropdownItem, - GlDropdownSectionHeader, GlSearchBoxByType, - TooltipOnTruncate, }, props: { selectText: { @@ -45,11 +39,6 @@ export default { required: false, default: () => [], }, - groupedOptions: { - type: Array, - required: false, - default: () => [], - }, isLoading: { type: Boolean, required: false, @@ -70,6 +59,11 @@ export default { required: false, default: false, }, + customIsSelectedOption: { + type: Function, + required: false, + default: undefined, + }, }, computed: { isSearchEmpty() { @@ -87,6 +81,9 @@ export default { } }, isSelected(option) { + if (this.customIsSelectedOption !== undefined) { + return this.customIsSelectedOption(option); + } if (Array.isArray(this.selected)) { return this.selected.some((label) => label.title === option.title); } @@ -143,7 +140,7 @@ export default { <gl-dropdown-form class="gl-relative gl-min-h-7" data-qa-selector="labels_dropdown_content"> <gl-loading-icon v-if="isLoading" - size="md" + size="lg" class="gl-absolute gl-left-0 gl-top-0 gl-right-0" /> <template v-else> @@ -177,36 +174,7 @@ export default { {{ option.title }} </slot> </gl-dropdown-item> - <template v-for="(optionGroup, index) in groupedOptions"> - <gl-dropdown-divider v-if="index !== 0" :key="index" /> - <gl-dropdown-section-header :key="optionGroup.id"> - <div class="gl-display-flex gl-max-w-full"> - <tooltip-on-truncate - :title="optionGroup.title" - class="gl-text-truncate gl-flex-grow-1" - > - {{ optionGroup.title }} - </tooltip-on-truncate> - <span v-if="optionGroup.secondaryText" class="gl-float-right gl-font-weight-normal"> - <gl-icon name="clock" class="gl-mr-2" /> - {{ optionGroup.secondaryText }} - </span> - </div> - </gl-dropdown-section-header> - <gl-dropdown-item - v-for="option in optionGroup.options" - :key="optionKey(option)" - :is-checked="isSelected(option)" - is-check-centered - is-check-item - data-testid="unselected-option" - @click="selectOption(option)" - > - <slot name="item" :item="option"> - {{ option.title }} - </slot> - </gl-dropdown-item> - </template> + <slot v-bind="{ isSelected }" name="grouped-options"></slot> <gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!"> {{ $options.i18n.noMatchingResults }} </gl-dropdown-item> 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 b0c1c1531aa..680f229d5e8 100644 --- a/app/assets/javascripts/vue_shared/components/file_finder/index.vue +++ b/app/assets/javascripts/vue_shared/components/file_finder/index.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlLoadingIcon } from '@gitlab/ui'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; import Mousetrap from 'mousetrap'; import VirtualList from 'vue-virtual-scroll-list'; @@ -9,13 +9,13 @@ import Item from './item.vue'; export const MAX_FILE_FINDER_RESULTS = 40; export const FILE_FINDER_ROW_HEIGHT = 55; -export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; const originalStopCallback = Mousetrap.prototype.stopCallback; export default { components: { GlIcon, + GlLoadingIcon, Item, VirtualList, }, @@ -71,7 +71,7 @@ export default { return this.filteredBlobsLength ? Math.min(this.filteredBlobsLength, 5) : 1; }, listHeight() { - return this.filteredBlobsLength ? FILE_FINDER_ROW_HEIGHT : FILE_FINDER_EMPTY_ROW_HEIGHT; + return FILE_FINDER_ROW_HEIGHT; }, showClearInputButton() { return this.searchText.trim() !== ''; @@ -265,9 +265,9 @@ export default { </li> </template> <li v-else class="dropdown-menu-empty-item"> - <div class="gl-mr-3 gl-ml-3 gl-mt-3 gl-mb-3"> + <div class="gl-mr-3 gl-ml-3 gl-mt-5 gl-mb-3"> <template v-if="loading"> - {{ __('Loading...') }} + <gl-loading-icon /> </template> <template v-else> {{ __('No files found.') }} diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js index 3d48c74b40b..d7a84798e47 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js @@ -1,6 +1,6 @@ import { __ } from '~/locale'; -export const DEBOUNCE_DELAY = 200; +export const DEBOUNCE_DELAY = 500; export const MAX_RECENT_TOKENS_SIZE = 3; export const FILTER_NONE = 'None'; 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 6638a5de62f..33d507dad57 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 @@ -89,32 +89,20 @@ export default { required: false, default: () => ({}), }, + syncFilterAndSort: { + type: Boolean, + required: false, + default: false, + }, }, data() { - let selectedSortOption = this.sortOptions[0]?.sortDirection?.descending; - let selectedSortDirection = SortDirection.descending; - - // Extract correct sortBy value based on initialSortBy - if (this.initialSortBy) { - selectedSortOption = this.sortOptions - .filter( - (sortBy) => - sortBy.sortDirection.ascending === this.initialSortBy || - sortBy.sortDirection.descending === this.initialSortBy, - ) - .pop(); - selectedSortDirection = Object.keys(selectedSortOption.sortDirection).find( - (key) => selectedSortOption.sortDirection[key] === this.initialSortBy, - ); - } - return { initialRender: true, recentSearchesPromise: null, recentSearches: [], filterValue: this.initialFilterValue, - selectedSortOption, - selectedSortDirection, + selectedSortOption: this.sortOptions[0], + selectedSortDirection: SortDirection.descending, }; }, computed: { @@ -173,7 +161,20 @@ export default { return undefined; }, }, + watch: { + initialFilterValue(newValue) { + if (this.syncFilterAndSort) { + this.filterValue = newValue; + } + }, + initialSortBy(newValue) { + if (this.syncFilterAndSort) { + this.updateSelectedSortValues(newValue); + } + }, + }, created() { + this.updateSelectedSortValues(this.initialSortBy); if (this.recentSearchesStorageKey) this.setupRecentSearch(); }, methods: { @@ -309,12 +310,25 @@ export default { const cleared = true; this.$emit('onFilter', [], cleared); }, + updateSelectedSortValues(sort) { + if (!sort) { + return; + } + + this.selectedSortOption = this.sortOptions.find( + (sortBy) => + sortBy.sortDirection.ascending === sort || sortBy.sortDirection.descending === sort, + ); + this.selectedSortDirection = Object.keys(this.selectedSortOption.sortDirection).find( + (key) => this.selectedSortOption.sortDirection[key] === sort, + ); + }, }, }; </script> <template> - <div class="vue-filtered-search-bar-container d-md-flex"> + <div class="vue-filtered-search-bar-container gl-md-display-flex"> <gl-form-checkbox v-if="showCheckbox" class="gl-align-self-center" 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 696456be990..848c49c48c7 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 @@ -87,6 +87,7 @@ export default { :get-active-token-value="getActiveAuthor" :default-suggestions="defaultAuthors" :preloaded-suggestions="preloadedAuthors" + v-bind="$attrs" @fetch-suggestions="fetchAuthors" v-on="$listeners" > diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue index e7923e0b55e..c3a0a97a7ba 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue @@ -211,10 +211,22 @@ export default { @select="handleTokenValueSelected" > <template #view-token="viewTokenProps"> - <slot name="view-token" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot> + <slot + name="view-token" + :view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + ...viewTokenProps, + activeTokenValue, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" + ></slot> </template> <template #view="viewTokenProps"> - <slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot> + <slot + name="view" + :view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + ...viewTokenProps, + activeTokenValue, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" + ></slot> </template> <template v-if="suggestionsEnabled" #suggestions> <template v-if="showDefaultSuggestions"> 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 4ecfc1cf40c..aa5161ca93c 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 @@ -65,6 +65,7 @@ export default { :suggestions="branches" :suggestions-loading="loading" :get-active-token-value="getActiveBranch" + v-bind="$attrs" @fetch-suggestions="fetchBranches" v-on="$listeners" > diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue index 5a69751a2cc..210d814d22a 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue @@ -67,6 +67,7 @@ export default { :suggestions="emojis" :suggestions-loading="loading" :get-active-token-value="getActiveEmoji" + v-bind="$attrs" @fetch-suggestions="fetchEmojis" v-on="$listeners" > 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 3f7a8920f48..6f24955814c 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 @@ -104,6 +104,7 @@ export default { :suggestions="labels" :get-active-token-value="getActiveLabel" :default-suggestions="defaultLabels" + v-bind="$attrs" @fetch-suggestions="fetchLabels" v-on="$listeners" > 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 11c081ab4f8..69265d0fdc9 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 @@ -84,6 +84,7 @@ export default { :suggestions="milestones" :suggestions-loading="loading" :get-active-token-value="getActiveMilestone" + v-bind="$attrs" @fetch-suggestions="fetchMilestones" v-on="$listeners" > diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue index f353cc3a765..9e68c92af5d 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/release_token.vue @@ -66,6 +66,7 @@ export default { :suggestions="releases" :suggestions-loading="loading" :get-active-token-value="getActiveRelease" + v-bind="$attrs" @fetch-suggestions="fetchReleases" v-on="$listeners" > diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 722df3cc58b..1f309a19b14 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -111,6 +111,16 @@ export default { required: false, default: false, }, + showCommentToolBar: { + type: Boolean, + required: false, + default: true, + }, + restrictedToolBarItems: { + type: Array, + required: false, + default: () => [], + }, }, data() { return { @@ -331,7 +341,7 @@ export default { :enable-preview="enablePreview" :show-suggest-popover="showSuggestPopover" :suggestion-start-index="suggestionsStartIndex" - data-testid="markdownHeader" + :restricted-tool-bar-items="restrictedToolBarItems" @preview-markdown="showPreviewTab" @write-markdown="showWriteTab" @handleSuggestDismissed="() => $emit('handleSuggestDismissed')" @@ -350,6 +360,7 @@ export default { :markdown-docs-path="markdownDocsPath" :quick-actions-docs-path="quickActionsDocsPath" :can-attach-file="canAttachFile" + :show-comment-tool-bar="showCommentToolBar" /> </div> </div> @@ -362,8 +373,6 @@ export default { <suggestions v-if="hasSuggestion" :note-html="markdownPreview" - :from-line="lineNumber" - :from-content="lineContent" :line-type="lineType" :disabled="true" :suggestions="suggestions" diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index d0bd5046bf0..ba2b5eaa4f9 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -54,6 +54,11 @@ export default { required: false, default: true, }, + restrictedToolBarItems: { + type: Array, + required: false, + default: () => [], + }, }, data() { return { @@ -193,7 +198,10 @@ export default { <toolbar-button tag="**" :button-title=" - sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey }) + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { + modifierKey, + }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ " :shortcuts="$options.shortcuts.bold" icon="bold" @@ -201,22 +209,28 @@ export default { <toolbar-button tag="_" :button-title=" - sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey }) + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { + modifierKey, + }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ " :shortcuts="$options.shortcuts.italic" icon="italic" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('strikethrough')" tag="~~" :button-title=" + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ sprintf(s__('MarkdownEditor|Add strikethrough text (%{modifierKey}⇧X)'), { - modifierKey, + modifierKey /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */, }) " :shortcuts="$options.shortcuts.strikethrough" icon="strikethrough" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('quote')" :prepend="true" :tag="tag" :button-title="__('Insert a quote')" @@ -266,30 +280,37 @@ export default { tag="[{text}](url)" tag-select="url" :button-title=" - sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey }) + /* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ + sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { + modifierKey, + }) /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */ " :shortcuts="$options.shortcuts.link" icon="link" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('bullet-list')" :prepend="true" tag="- " :button-title="__('Add a bullet list')" icon="list-bulleted" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('numbered-list')" :prepend="true" tag="1. " :button-title="__('Add a numbered list')" icon="list-numbered" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('task-list')" :prepend="true" tag="- [ ] " :button-title="__('Add a task list')" icon="list-task" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('collapsible-section')" :tag="mdCollapsibleSection" :prepend="true" tag-select="Click to expand" @@ -297,12 +318,14 @@ export default { icon="details-block" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('table')" :tag="mdTable" :prepend="true" :button-title="__('Add a table')" icon="table" /> <toolbar-button + v-if="!restrictedToolBarItems.includes('full-screen')" class="js-zen-enter" :prepend="true" :button-title="__('Go full screen')" diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue index f1c293c87f4..6c99a749edc 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue @@ -24,6 +24,11 @@ export default { required: false, default: true, }, + showCommentToolBar: { + type: Boolean, + required: false, + default: true, + }, }, computed: { hasQuickActionsDocsPath() { @@ -34,24 +39,33 @@ export default { </script> <template> - <div class="comment-toolbar clearfix"> + <div v-if="showCommentToolBar" class="comment-toolbar clearfix"> <div class="toolbar-text"> <template v-if="!hasQuickActionsDocsPath && markdownDocsPath"> - <gl-link :href="markdownDocsPath" target="_blank"> - {{ __('Markdown is supported') }} - </gl-link> + <gl-sprintf + :message=" + s__('MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}') + " + > + <template #markdownDocsLink="{ content }"> + <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> </template> <template v-if="hasQuickActionsDocsPath && markdownDocsPath"> <gl-sprintf :message=" - __( - '%{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd} and %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd} are supported', + s__( + 'NoteToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}. For %{quickActionsDocsLinkStart}quick actions%{quickActionsDocsLinkEnd}, type %{keyboardStart}/%{keyboardEnd}.', ) " > <template #markdownDocsLink="{ content }"> <gl-link :href="markdownDocsPath" target="_blank">{{ content }}</gl-link> </template> + <template #keyboard="{ content }"> + <kbd>{{ content }}</kbd> + </template> <template #quickActionsDocsLink="{ content }"> <gl-link :href="quickActionsDocsPath" target="_blank">{{ content }}</gl-link> </template> diff --git a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue index 3e796a73f72..e23721da223 100644 --- a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue +++ b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_tab.vue @@ -90,7 +90,9 @@ export default { modal-id="upload-metric-modal" size="sm" :action-primary="actionPrimaryProps" - :action-cancel="{ text: $options.i18n.modalCancel }" + :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + text: $options.i18n.modalCancel, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" :title="$options.i18n.modalTitle" :visible="modalVisible" @hidden="clearInputs" diff --git a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue index 8eb8e52728d..bbbaaeb8a9e 100644 --- a/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue +++ b/app/assets/javascripts/vue_shared/components/metric_images/metric_images_table.vue @@ -159,7 +159,9 @@ export default { size="sm" :visible="modalVisible" :action-primary="deleteActionPrimaryProps" - :action-cancel="{ text: $options.i18n.modalCancel }" + :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + text: $options.i18n.modalCancel, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" @primary.prevent="onDelete" @hidden="resetEditFields" > @@ -177,7 +179,9 @@ export default { modal-id="edit-metric-modal" size="sm" :action-primary="updateActionPrimaryProps" - :action-cancel="{ text: $options.i18n.modalCancel }" + :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + text: $options.i18n.modalCancel, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" :visible="editModalVisible" data-testid="metric-image-edit-modal" @hidden="resetEditFields" diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue index a069d1cd756..21212e82de4 100644 --- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue +++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue @@ -61,7 +61,9 @@ export default { v-for="(tab, i) in tabs" :key="i" :title-link-class="`js-${scope}-tab-${tab.scope} gl-display-inline-flex`" - :title-link-attributes="{ 'data-testid': `${scope}-tab-${tab.scope}` }" + :title-link-attributes="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + 'data-testid': `${scope}-tab-${tab.scope}`, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" :active="tab.isActive" @click="onTabClick(tab)" > diff --git a/app/assets/javascripts/vue_shared/components/paginated_list.vue b/app/assets/javascripts/vue_shared/components/paginated_list.vue index e19b8510399..ddc7a457b98 100644 --- a/app/assets/javascripts/vue_shared/components/paginated_list.vue +++ b/app/assets/javascripts/vue_shared/components/paginated_list.vue @@ -29,7 +29,7 @@ export default { </template> <template #default="{ listItem, query }"> - <slot :listItem="listItem" :query="query"></slot> + <slot :list-item="listItem" :query="query"></slot> </template> </gl-paginated-list> </template> 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 6bb321713d5..a8b250f2041 100644 --- a/app/assets/javascripts/vue_shared/components/registry/list_item.vue +++ b/app/assets/javascripts/vue_shared/components/registry/list_item.vue @@ -32,7 +32,6 @@ export default { return { 'gl-border-t-transparent': !this.first && !this.selected, 'gl-border-t-gray-100': this.first && !this.selected, - 'gl-opacity-5': this.disabled, 'gl-border-b-gray-100': !this.selected, 'gl-bg-blue-50 gl-border-blue-200': this.selected, }; diff --git a/app/assets/javascripts/vue_shared/components/registry/registry_search.vue b/app/assets/javascripts/vue_shared/components/registry/registry_search.vue index 767a108dde5..da68fe961a6 100644 --- a/app/assets/javascripts/vue_shared/components/registry/registry_search.vue +++ b/app/assets/javascripts/vue_shared/components/registry/registry_search.vue @@ -116,6 +116,7 @@ export default { @clear="clearSearch" /> <gl-sorting + data-testid="registry-sort-dropdown" :text="sortText" :is-ascending="isSortAscending" @sortDirectionChange="onDirectionChange" diff --git a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue index d5493aa5a66..9eaaf7d1c18 100644 --- a/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue +++ b/app/assets/javascripts/vue_shared/components/runner_instructions/runner_instructions_modal.vue @@ -50,6 +50,11 @@ export default { required: false, default: null, }, + defaultPlatformName: { + type: String, + required: false, + default: null, + }, }, apollo: { platforms: { @@ -64,9 +69,10 @@ export default { }); }, result() { - // Select first platform by default - if (this.platforms?.[0]) { - this.selectPlatform(this.platforms[0]); + if (this.platforms.length) { + // If it is set and available, select the defaultSelectedPlatform. + // Otherwise, select the first available platform + this.selectPlatform(this.defaultPlatform() || this.platforms[0]); } }, error() { @@ -138,6 +144,14 @@ export default { show() { this.$refs.modal.show(); }, + focusSelected() { + // By default the first platform always gets the focus, but when the `defaultPlatformName` + // property is present, any other platform might actually be selected. + this.$refs[this.selectedPlatformName]?.[0].$el.focus(); + }, + defaultPlatform() { + return this.platforms.find((platform) => platform.name === this.defaultPlatformName); + }, selectPlatform(platform) { this.selectedPlatform = platform; @@ -182,6 +196,8 @@ export default { :title="$options.i18n.installARunner" :action-secondary="$options.closeButton" v-bind="$attrs" + v-on="$listeners" + @shown="focusSelected" > <gl-alert v-if="showAlert" variant="danger" @dismiss="toggleAlert(false)"> {{ $options.i18n.fetchError }} @@ -203,6 +219,7 @@ export default { <gl-button v-for="platform in platforms" :key="platform.name" + :ref="platform.name" :selected="selectedPlatform && selectedPlatform.name === platform.name" @click="selectPlatform(platform)" > diff --git a/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue new file mode 100644 index 00000000000..f50706b6de8 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/segmented_control_button_group.vue @@ -0,0 +1,35 @@ +<script> +import { GlButtonGroup, GlButton } from '@gitlab/ui'; + +// TODO: We're planning to move this component to GitLab UI +// https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1787 +export default { + components: { + GlButtonGroup, + GlButton, + }, + props: { + options: { + type: Array, + required: true, + }, + value: { + type: [String, Number, Boolean], + required: true, + }, + }, +}; +</script> +<template> + <gl-button-group> + <gl-button + v-for="opt in options" + :key="opt.value" + :disabled="!!opt.disabled" + :selected="value === opt.value" + @click="$emit('input', opt.value)" + > + <slot name="button-content" v-bind="opt">{{ opt.text }}</slot> + </gl-button> + </gl-button-group> +</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue index 12daaea8758..dfa2ca2d20c 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/issuable_move_dropdown.vue @@ -175,7 +175,7 @@ export default { :debounce="300" /> <div data-testid="content" class="dropdown-content"> - <gl-loading-icon v-if="projectsListLoading" size="md" class="gl-p-5" /> + <gl-loading-icon v-if="projectsListLoading" size="lg" class="gl-p-5" /> <ul v-else> <gl-dropdown-item v-for="project in projects" @@ -199,7 +199,7 @@ export default { > <gl-button category="primary" - variant="success" + variant="confirm" :disabled="!Boolean(selectedProject)" class="gl-text-center! issuable-move-button" @click="handleMoveClick" 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 3ec33a653b8..2cccb8325f4 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 @@ -104,7 +104,7 @@ export default { <gl-button :disabled="disableCreate" category="primary" - variant="success" + variant="confirm" class="float-left d-flex align-items-center" @click="handleCreateClick" > diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue index 623e7799493..134575b7a27 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view.vue @@ -185,7 +185,7 @@ export default { <gl-loading-icon v-if="labelsFetchInProgress" class="labels-fetch-loading gl-align-items-center w-100 h-100" - size="md" + size="lg" /> <ul v-else class="list-unstyled gl-mb-0 gl-word-break-word"> <label-item diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue index 7989ad40b5a..e91a0489ef1 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/dropdown_title.vue @@ -23,7 +23,7 @@ export default { </script> <template> - <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900"> + <div class="hide-collapsed gl-line-height-20 gl-mb-2 gl-text-gray-900 gl-font-weight-bold"> {{ __('Labels') }} <template v-if="allowLabelEdit"> <gl-loading-icon v-show="labelsSelectInProgress" size="sm" inline /> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue index 88977652556..090bf9493bf 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue @@ -179,7 +179,7 @@ export default { <gl-button :disabled="disableCreate" category="primary" - variant="success" + variant="confirm" class="gl-display-flex gl-align-items-center" data-testid="create-button" @click="createLabel" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue index ae179ef93c7..f595e635f2c 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue @@ -147,7 +147,7 @@ export default { <gl-loading-icon v-if="labelsFetchInProgress" class="labels-fetch-loading gl-align-items-center gl-w-full gl-h-full gl-mb-3" - size="md" + size="lg" /> <template v-else> <gl-dropdown-item diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue index 1b8e4bcfec6..c30ca5369ee 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk_line.vue @@ -36,6 +36,9 @@ export default { return content; }, + firstLineClass() { + return { 'gl-mt-3!': this.number === 1 }; + }, }, methods: { wrapBidiChar(bidiChar) { @@ -56,10 +59,11 @@ export default { </script> <template> <div class="gl-display-flex"> - <div class="line-numbers gl-pt-0! gl-pb-0! gl-absolute gl-z-index-3"> + <div class="gl-p-0! gl-absolute gl-z-index-3 gl-border-r diff-line-num line-numbers"> <gl-link :id="`L${number}`" - class="file-line-num diff-line-num gl-user-select-none" + class="gl-user-select-none gl-ml-5 gl-pr-3 gl-shadow-none! file-line-num diff-line-num" + :class="firstLineClass" :to="`#L${number}`" :data-line-number="number" > @@ -68,7 +72,8 @@ export default { </div> <pre - class="code highlight gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11!" + class="gl-p-0! gl-w-full gl-overflow-visible! gl-ml-11! gl-border-none! code highlight" + :class="firstLineClass" ><code><span :id="`LC${number}`" v-safe-html="formattedContent" :lang="language" class="line" data-testid="content"></span></code></pre> </div> </template> diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue index edf2229a9a1..ed87a202b15 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/source_viewer.vue @@ -2,6 +2,7 @@ import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui'; import LineHighlighter from '~/blob/line_highlighter'; import eventHub from '~/notes/event_hub'; +import languageLoader from '~/content_editor/services/highlight_js_language_loader'; import { ROUGE_TO_HLJS_LANGUAGE_MAP, LINES_PER_CHUNK } from './constants'; import Chunk from './components/chunk.vue'; @@ -129,7 +130,7 @@ export default { let languageDefinition; try { - languageDefinition = await import(`highlight.js/lib/languages/${this.language}`); + languageDefinition = await languageLoader[this.language](); this.hljs.registerLanguage(this.language, languageDefinition.default); } catch (message) { this.$emit('error', message); diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue index e784bba6698..0d466df1b7f 100644 --- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue +++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue @@ -42,6 +42,6 @@ export default { :class="cssClass" :title="tooltipTitle(time)" :datetime="time" - ><slot :timeAgo="timeAgo">{{ timeAgo }}</slot></time + ><slot :time-ago="timeAgo">{{ timeAgo }}</slot></time > </template> 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 62de76e46b5..f62bf686f85 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 @@ -160,7 +160,7 @@ export default { > <gl-icon name="upload" :size="iconStyles.size" :class="iconStyles.class" /> <p class="gl-mb-0" data-testid="upload-text"> - <slot name="upload-text" :openFileUpload="openFileUpload"> + <slot name="upload-text" :open-file-upload="openFileUpload"> <gl-sprintf :message=" singleFileSelection diff --git a/app/assets/javascripts/vue_shared/components/usage_quotas/usage_banner.vue b/app/assets/javascripts/vue_shared/components/usage_quotas/usage_banner.vue new file mode 100644 index 00000000000..bc5e0cf10dd --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/usage_quotas/usage_banner.vue @@ -0,0 +1,68 @@ +<script> +import { GlSkeletonLoader } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { s__ } from '~/locale'; + +export default { + name: 'UsageBanner', + components: { + GlSkeletonLoader, + }, + props: { + loading: { + type: Boolean, + required: false, + default: false, + }, + }, + i18n: { + dependencyProxy: s__('UsageQuota|Dependency proxy'), + storageUsed: s__('UsageQuota|Storage used'), + dependencyProxyMessage: s__( + 'UsageQuota|Local proxy used for frequently-accessed upstream Docker images. %{linkStart}More information%{linkEnd}', + ), + }, + storageUsageQuotaHelpPage: helpPagePath('user/usage_quotas'), +}; +</script> +<template> + <div class="gl-display-flex gl-flex-direction-column"> + <div class="gl-display-flex gl-align-items-center gl-py-3"> + <div + class="gl-display-flex gl-xs-flex-direction-column gl-justify-content-space-between gl-align-items-stretch gl-flex-grow-1" + > + <div class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1"> + <div + v-if="$slots['left-primary-text']" + class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0 gl-mb-4" + > + <slot name="left-primary-text"></slot> + </div> + <div + v-if="$slots['left-secondary-text']" + class="gl-display-flex gl-align-items-center gl-text-gray-500 gl-min-h-6 gl-min-w-0 gl-flex-grow-1 gl-w-70p gl-md-max-w-70p" + > + <slot name="left-secondary-text"></slot> + </div> + </div> + <div + class="gl-display-flex gl-flex-direction-column gl-sm-align-items-flex-end gl-justify-content-space-between gl-text-gray-500 gl-flex-shrink-0" + > + <div + v-if="$slots['right-primary-text']" + class="gl-display-flex gl-align-items-center gl-sm-text-body gl-sm-font-weight-bold gl-min-h-6" + > + <slot name="right-primary-text"></slot> + </div> + <div + v-if="$slots['right-secondary-text']" + class="gl-display-flex gl-align-items-center gl-min-h-6" + > + <slot v-if="!loading" name="right-secondary-text"></slot> + <gl-skeleton-loader v-else :width="60" :lines="1" /> + </div> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index cac8f0a9aa5..ec7a7cd72ae 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -6,9 +6,13 @@ import { GlIcon, GlSafeHtmlDirective, GlSprintf, + GlButton, } from '@gitlab/ui'; +import { __ } from '~/locale'; import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; import { glEmojiTag } from '~/emoji'; +import createFlash from '~/flash'; +import { followUser, unfollowUser } from '~/rest_api'; import UserAvatarImage from '../user_avatar/user_avatar_image.vue'; const MAX_SKELETON_LINES = 4; @@ -24,6 +28,7 @@ export default { UserAvatarImage, UserNameWithStatus, GlSprintf, + GlButton, }, directives: { SafeHtml: GlSafeHtmlDirective, @@ -38,6 +43,16 @@ export default { required: true, default: null, }, + placement: { + type: String, + required: false, + default: 'top', + }, + }, + data() { + return { + toggleFollowLoading: false, + }; }, computed: { statusHtml() { @@ -59,6 +74,59 @@ export default { availabilityStatus() { return this.user?.status?.availability || ''; }, + isNotCurrentUser() { + return !this.userIsLoading && this.user.username !== gon.current_username; + }, + shouldRenderToggleFollowButton() { + return this.isNotCurrentUser && typeof this.user?.isFollowed !== 'undefined'; + }, + toggleFollowButtonText() { + if (this.toggleFollowLoading) return null; + + return this.user?.isFollowed ? __('Unfollow') : __('Follow'); + }, + toggleFollowButtonVariant() { + return this.user?.isFollowed ? 'default' : 'confirm'; + }, + }, + methods: { + async toggleFollow() { + if (this.user.isFollowed) { + this.unfollow(); + } else { + this.follow(); + } + }, + async follow() { + this.toggleFollowLoading = true; + try { + await followUser(this.user.id); + this.$emit('follow'); + } catch (error) { + createFlash({ + message: __('An error occurred while trying to follow this user, please try again.'), + error, + captureError: true, + }); + } finally { + this.toggleFollowLoading = false; + } + }, + async unfollow() { + this.toggleFollowLoading = true; + try { + await unfollowUser(this.user.id); + this.$emit('unfollow'); + } catch (error) { + createFlash({ + message: __('An error occurred while trying to unfollow this user, please try again.'), + error, + captureError: true, + }); + } finally { + this.toggleFollowLoading = false; + } + }, }, safeHtmlConfig: { ADD_TAGS: ['gl-emoji'] }, }; @@ -66,12 +134,24 @@ export default { <template> <!-- 200ms delay so not every mouseover triggers Popover --> - <gl-popover :target="target" :delay="200" boundary="viewport" placement="top"> + <gl-popover :target="target" :delay="200" :placement="placement" boundary="viewport"> <div class="gl-p-3 gl-line-height-normal gl-display-flex" data-testid="user-popover"> - <div class="gl-p-2 flex-shrink-1"> - <user-avatar-image :img-src="user.avatarUrl" :size="64" css-classes="gl-mr-3!" /> + <div + class="gl-p-2 flex-shrink-1 gl-display-flex gl-flex-direction-column align-items-center gl-w-70p" + > + <user-avatar-image :img-src="user.avatarUrl" :size="64" css-classes="gl-m-0!" /> + <div v-if="shouldRenderToggleFollowButton" class="gl-mt-3"> + <gl-button + :variant="toggleFollowButtonVariant" + :loading="toggleFollowLoading" + size="small" + data-testid="toggle-follow-button" + @click="toggleFollow" + >{{ toggleFollowButtonText }}</gl-button + > + </div> </div> - <div class="gl-p-2 gl-w-full gl-min-w-0"> + <div class="gl-w-full gl-min-w-0 gl-word-break-word"> <template v-if="userIsLoading"> <gl-skeleton-loader :lines="$options.maxSkeletonLines" @@ -94,7 +174,7 @@ export default { <div class="gl-text-gray-500"> <div v-if="user.bio" class="gl-display-flex gl-mb-2"> <gl-icon name="profile" class="gl-flex-shrink-0" /> - <span ref="bio" class="gl-ml-2 gl-overflow-hidden">{{ user.bio }}</span> + <span ref="bio" class="gl-ml-2">{{ user.bio }}</span> </div> <div v-if="user.workInformation" class="gl-display-flex gl-mb-2"> <gl-icon name="work" class="gl-flex-shrink-0" /> diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue index 9df5254155e..91f20863089 100644 --- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue +++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue @@ -298,7 +298,7 @@ export default { <gl-loading-icon v-if="isLoading" data-testid="loading-participants" - size="md" + size="lg" class="gl-absolute gl-left-0 gl-top-0 gl-right-0" /> <template v-else> diff --git a/app/assets/javascripts/vue_shared/constants.js b/app/assets/javascripts/vue_shared/constants.js index 9cb66f6e65f..3ebeec4a50b 100644 --- a/app/assets/javascripts/vue_shared/constants.js +++ b/app/assets/javascripts/vue_shared/constants.js @@ -1,4 +1,5 @@ -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; +import { IssuableType, WorkspaceType } from '~/issues/constants'; const INTERVALS = { minute: 'minute', @@ -66,3 +67,14 @@ export const getTimeWindow = (timeWindowName) => export const AVATAR_SHAPE_OPTION_CIRCLE = 'circle'; export const AVATAR_SHAPE_OPTION_RECT = 'rect'; + +export const confidentialityInfoText = (workspaceType, issuableType) => + sprintf( + __( + 'Only %{workspaceType} members with at least Reporter role can view or be notified about this %{issuableType}.', + ), + { + workspaceType: workspaceType === WorkspaceType.project ? __('project') : __('group'), + issuableType: issuableType === IssuableType.Issue ? __('issue') : __('epic'), + }, + ); diff --git a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue index c216a05bdb0..0758cb507e9 100644 --- a/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue +++ b/app/assets/javascripts/vue_shared/issuable/create/components/issuable_form.vue @@ -1,5 +1,5 @@ <script> -import { GlForm, GlFormInput } from '@gitlab/ui'; +import { GlForm, GlFormInput, GlFormGroup } from '@gitlab/ui'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants'; import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; @@ -9,6 +9,7 @@ export default { components: { GlForm, GlFormInput, + GlFormGroup, MarkdownField, LabelsSelect, }, @@ -37,6 +38,7 @@ export default { selectedLabels: [], }; }, + computed: {}, methods: { handleUpdateSelectedLabels(labels) { if (labels.length) { @@ -52,12 +54,15 @@ export default { <div data-testid="issuable-title" class="form-group row"> <label for="issuable-title" class="col-form-label col-sm-2">{{ __('Title') }}</label> <div class="col-sm-10"> - <gl-form-input - id="issuable-title" - v-model="issuableTitle" - :autofocus="true" - :placeholder="__('Title')" - /> + <gl-form-group :description="__('Maximum of 255 characters')"> + <gl-form-input + id="issuable-title" + v-model="issuableTitle" + maxlength="255" + :autofocus="true" + :placeholder="__('Title')" + /> + </gl-form-group> </div> </div> <div data-testid="issuable-description" class="form-group row"> diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue index 8008b85bbdb..6453290f6ea 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue @@ -55,7 +55,7 @@ export default { return createdSecondsAgo < SECONDS_IN_DAY; }, author() { - return this.issuable.author; + return this.issuable.author || {}; }, webUrl() { return this.issuable.gitlabWebUrl || this.issuable.webUrl; @@ -215,7 +215,7 @@ export default { <span class="gl-display-none gl-sm-display-inline"> <span aria-hidden="true">·</span> <span class="issuable-authored gl-mr-3"> - <gl-sprintf :message="__('created %{timeAgo} by %{author}')"> + <gl-sprintf v-if="author.name" :message="__('created %{timeAgo} by %{author}')"> <template #timeAgo> <span v-gl-tooltip.bottom @@ -241,6 +241,17 @@ export default { </gl-link> </template> </gl-sprintf> + <gl-sprintf v-else :message="__('created %{timeAgo}')"> + <template #timeAgo> + <span + v-gl-tooltip.bottom + :title="tooltipTitle(issuable.createdAt)" + data-testid="issuable-created-at" + > + {{ createdAt }} + </span> + </template> + </gl-sprintf> </span> <slot name="timeframe"></slot> </span> diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue index 20f178dfb7d..8b293b2e9f6 100644 --- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue +++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue @@ -168,6 +168,11 @@ export default { required: false, default: '', }, + syncFilterAndSort: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -282,6 +287,7 @@ export default { :sort-options="sortOptions" :initial-filter-value="initialFilterValue" :initial-sort-by="initialSortBy" + :sync-filter-and-sort="syncFilterAndSort" :show-checkbox="showBulkEditSidebar" :checkbox-checked="allIssuablesChecked" class="gl-flex-grow-1 gl-border-t-none row-content-block" diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue index ee7e113af72..649dbd6576b 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_header.vue @@ -1,14 +1,23 @@ <script> -import { GlIcon, GlButton, GlTooltipDirective, GlAvatarLink, GlAvatarLabeled } from '@gitlab/ui'; +import { + GlIcon, + GlBadge, + GlButton, + GlTooltipDirective, + GlAvatarLink, + GlAvatarLabeled, +} from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { isExternal } from '~/lib/utils/url_utility'; import { n__, sprintf } from '~/locale'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { IssuableStates } from '~/vue_shared/issuable/list/constants'; export default { components: { GlIcon, + GlBadge, GlButton, GlAvatarLink, GlAvatarLabeled, @@ -26,6 +35,11 @@ export default { type: Object, required: true, }, + issuableState: { + type: String, + required: false, + default: '', + }, statusBadgeClass: { type: String, required: false, @@ -36,6 +50,11 @@ export default { required: false, default: '', }, + statusIconClass: { + type: String, + required: false, + default: '', + }, blocked: { type: Boolean, required: false, @@ -53,6 +72,9 @@ export default { }, }, computed: { + badgeVariant() { + return this.issuableState === IssuableStates.Opened ? 'success' : 'info'; + }, authorId() { return getIdFromGraphQLId(`${this.author.id}`); }, @@ -71,6 +93,9 @@ export default { { completedCount, count }, ); }, + hasTasks() { + return this.taskCompletionStatus.count > 0; + }, }, mounted() { this.toggleSidebarButtonEl = document.querySelector('.js-toggle-right-sidebar-button'); @@ -88,10 +113,15 @@ export default { <template> <div class="detail-page-header"> <div class="detail-page-header-body"> - <div data-testid="status" class="issuable-status-box status-box" :class="statusBadgeClass"> - <gl-icon v-if="statusIcon" :name="statusIcon" class="d-block d-sm-none" /> - <span class="d-none d-sm-block"><slot name="status-badge"></slot></span> - </div> + <gl-badge + data-testid="status" + class="issuable-status-badge gl-mr-3" + :class="statusBadgeClass" + :variant="badgeVariant" + > + <gl-icon v-if="statusIcon" :name="statusIcon" :class="statusIconClass" /> + <span class="gl-display-none gl-sm-display-block"><slot name="status-badge"></slot></span> + </gl-badge> <div class="issuable-meta gl-display-flex gl-align-items-center d-md-inline-block"> <div v-if="blocked || confidential" class="gl-display-inline-block"> <div v-if="blocked" data-testid="blocked" class="issuable-warning-icon inline"> @@ -128,7 +158,7 @@ export default { <strong class="author d-sm-none d-inline">@{{ author.username }}</strong> </gl-avatar-link> <span - v-if="taskCompletionStatus" + v-if="taskCompletionStatus && hasTasks" data-testid="task-status" class="gl-display-none gl-md-display-block gl-lg-display-inline-block" >{{ taskStatusString }}</span diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue index 8849af2a52e..c165ee91c59 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_show_root.vue @@ -27,6 +27,11 @@ export default { required: false, default: '', }, + statusIconClass: { + type: String, + required: false, + default: '', + }, enableEdit: { type: Boolean, required: false, @@ -102,8 +107,10 @@ export default { <template> <div class="issuable-show-container" data-qa-selector="issuable_show_container"> <issuable-header + :issuable-state="issuable.state" :status-badge-class="statusBadgeClass" :status-icon="statusIcon" + :status-icon-class="statusIconClass" :blocked="issuable.blocked" :confidential="issuable.confidential" :created-at="issuable.createdAt" @@ -122,6 +129,7 @@ export default { :issuable="issuable" :status-badge-class="statusBadgeClass" :status-icon="statusIcon" + :status-icon-class="statusIconClass" :enable-edit="enableEdit" :enable-autocomplete="enableAutocomplete" :enable-autosave="enableAutosave" diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue index 45941174a62..47f05a2cee2 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue @@ -86,7 +86,7 @@ export default { > <p data-testid="status" - class="issuable-status-box status-box gl-my-0" + class="issuable-status-box status-box gl-white-space-nowrap gl-my-0" :class="statusBadgeClass" > <gl-icon :name="statusIcon" class="gl-display-block d-sm-none gl-h-6!" /> diff --git a/app/assets/javascripts/vue_shared/mixins/timeago.js b/app/assets/javascripts/vue_shared/mixins/timeago.js index c5f41d81167..2a0256548a8 100644 --- a/app/assets/javascripts/vue_shared/mixins/timeago.js +++ b/app/assets/javascripts/vue_shared/mixins/timeago.js @@ -1,4 +1,4 @@ -import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; +import { formatDate, getTimeago, timeagoLanguageCode } from '~/lib/utils/datetime_utility'; /** * Mixin with time ago methods used in some vue components @@ -8,7 +8,7 @@ export default { timeFormatted(time) { const timeago = getTimeago(); - return timeago.format(time); + return timeago.format(time, timeagoLanguageCode); }, tooltipTitle(time) { diff --git a/app/assets/javascripts/vue_shared/security_configuration/components/section_layout.vue b/app/assets/javascripts/vue_shared/security_configuration/components/section_layout.vue new file mode 100644 index 00000000000..6045d75ac11 --- /dev/null +++ b/app/assets/javascripts/vue_shared/security_configuration/components/section_layout.vue @@ -0,0 +1,34 @@ +<script> +import SectionLoader from './section_loader.vue'; + +export default { + name: 'SectionLayout', + components: { + SectionLoader, + }, + props: { + heading: { + type: String, + required: true, + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + }, +}; +</script> + +<template> + <div class="row gl-m-0 gl-border-b gl-line-height-20 gl-py-6"> + <div class="col-lg-4 gl-pl-0 gl-pr-9"> + <h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2> + <slot name="description"></slot> + </div> + <div class="col-lg-8 gl-pr-0 gl-pl-0"> + <section-loader v-if="isLoading" /> + <slot v-else name="features"></slot> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/security_configuration/components/section_loader.vue b/app/assets/javascripts/vue_shared/security_configuration/components/section_loader.vue new file mode 100644 index 00000000000..b15e25b0943 --- /dev/null +++ b/app/assets/javascripts/vue_shared/security_configuration/components/section_loader.vue @@ -0,0 +1,35 @@ +<script> +import { GlCard, GlSkeletonLoader } from '@gitlab/ui'; + +export default { + name: 'SectionLoader', + components: { + GlCard, + GlSkeletonLoader, + }, +}; +</script> + +<template> + <div> + <gl-skeleton-loader :width="1248" :height="180"> + <rect x="0" y="0" width="100" height="15" rx="4" /> + <rect x="0" y="24" width="460" height="32" rx="4" /> + <rect x="0" y="71" width="100" height="15" rx="4" /> + <rect x="0" y="95" width="460" height="72" rx="4" /> + </gl-skeleton-loader> + <gl-card v-for="i in 2" :key="i" class="gl-mb-5"> + <template #header> + <gl-skeleton-loader :width="1248" :height="15"> + <rect x="0" y="0" width="300" height="15" rx="4" /> + </gl-skeleton-loader> + </template> + <gl-skeleton-loader :width="1248" :height="15"> + <rect x="0" y="0" width="600" height="15" rx="4" /> + </gl-skeleton-loader> + <gl-skeleton-loader :width="1248" :height="15"> + <rect x="0" y="0" width="300" height="15" rx="4" /> + </gl-skeleton-loader> + </gl-card> + </div> +</template> 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 458bacce915..6a4f671abb9 100644 --- a/app/assets/javascripts/vue_shared/security_reports/store/utils.js +++ b/app/assets/javascripts/vue_shared/security_reports/store/utils.js @@ -90,7 +90,7 @@ const createStatusMessage = ({ reportType, status, total }) => { if (status) { message = __('%{reportType} %{status}'); } else if (!total) { - message = __('%{reportType} detected %{totalStart}no%{totalEnd} vulnerabilities.'); + message = __('%{reportType} detected no %{totalStart}new%{totalEnd} vulnerabilities.'); } else { message = __( '%{reportType} detected %{totalStart}%{total}%{totalEnd} potential %{vulnMessage}', |