diff options
Diffstat (limited to 'app/assets')
20 files changed, 203 insertions, 39 deletions
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index f65e22a31c5..f56c402091f 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -7,6 +7,7 @@ import csrf from './lib/utils/csrf'; import axios from './lib/utils/axios_utils'; import { n__, __ } from '~/locale'; import { getFilename } from '~/lib/utils/file_upload'; +import { spriteIcon } from '~/lib/utils/common_utils'; Dropzone.autoDiscover = false; @@ -25,7 +26,7 @@ function getErrorMessage(res) { export default function dropzoneInput(form, config = { parallelUploads: 2 }) { const divHover = '<div class="div-dropzone-hover"></div>'; - const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>'; + const iconPaperclip = spriteIcon('paperclip', 'div-dropzone-icon'); const $attachButton = form.find('.button-attach-file'); const $attachingFileMessage = form.find('.attaching-file-message'); const $cancelButton = form.find('.button-cancel-uploading-files'); diff --git a/app/assets/javascripts/issuable_list/components/issuable_item.vue b/app/assets/javascripts/issuable_list/components/issuable_item.vue index d8cb1ab07cd..283e49c287a 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_item.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_item.vue @@ -1,15 +1,20 @@ <script> -import { GlLink, GlLabel, GlTooltipDirective } from '@gitlab/ui'; +import { GlLink, GlIcon, GlLabel, GlTooltipDirective } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getTimeago } from '~/lib/utils/datetime_utility'; import { isScopedLabel } from '~/lib/utils/common_utils'; import timeagoMixin from '~/vue_shared/mixins/timeago'; +import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; + export default { components: { GlLink, + GlIcon, GlLabel, + IssuableAssignees, }, directives: { GlTooltip: GlTooltipDirective, @@ -24,25 +29,33 @@ export default { type: Object, required: true, }, + enableLabelPermalinks: { + type: Boolean, + required: true, + }, }, computed: { author() { return this.issuable.author; }, authorId() { - const id = parseInt(this.author.id, 10); - - if (Number.isNaN(id)) { - return this.author.id.includes('gid') - ? this.author.id.split('gid://gitlab/User/').pop() - : ''; + return getIdFromGraphQLId(`${this.author.id}`); + }, + isIssuableUrlExternal() { + // Check if URL is relative, which means it is internal. + if (!/^https?:\/\//g.test(this.issuable.webUrl)) { + return false; } - - return id; + // In case URL is absolute, it may or may not be internal, + // hence use `gon.gitlab_url` which is current instance domain. + return !this.issuable.webUrl.includes(gon.gitlab_url); }, labels() { return this.issuable.labels?.nodes || this.issuable.labels || []; }, + assignees() { + return this.issuable.assignees || []; + }, createdAt() { return sprintf(__('created %{timeAgo}'), { timeAgo: getTimeago().format(this.issuable.createdAt), @@ -53,11 +66,33 @@ export default { timeAgo: getTimeago().format(this.issuable.updatedAt), }); }, + issuableTitleProps() { + if (this.isIssuableUrlExternal) { + return { + target: '_blank', + }; + } + return {}; + }, }, methods: { + hasSlotContents(slotName) { + return Boolean(this.$slots[slotName]); + }, scopedLabel(label) { return isScopedLabel(label); }, + labelTitle(label) { + return label.title || label.name; + }, + labelTarget(label) { + if (this.enableLabelPermalinks) { + const key = encodeURIComponent('label_name[]'); + const value = encodeURIComponent(this.labelTitle(label)); + return `?${key}=${value}`; + } + return '#'; + }, /** * This is needed as an independent method since * when user changes current page, `$refs.authorLink` @@ -74,17 +109,20 @@ export default { </script> <template> - <li class="issue"> + <li class="issue px-3"> <div class="issue-box"> <div class="issuable-info-container"> <div class="issuable-main-info"> <div data-testid="issuable-title" class="issue-title title"> <span class="issue-title-text" dir="auto"> - <gl-link :href="issuable.webUrl">{{ issuable.title }}</gl-link> + <gl-link :href="issuable.webUrl" v-bind="issuableTitleProps" + >{{ issuable.title }}<gl-icon v-if="isIssuableUrlExternal" name="external-link" + /></gl-link> </span> </div> <div class="issuable-info"> - <span data-testid="issuable-reference" class="issuable-reference" + <slot v-if="hasSlotContents('reference')" name="reference"></slot> + <span v-else data-testid="issuable-reference" class="issuable-reference" >{{ issuableSymbol }}{{ issuable.iid }}</span > <span class="issuable-authored d-none d-sm-inline-block"> @@ -113,15 +151,30 @@ export default { v-for="(label, index) in labels" :key="index" :background-color="label.color" - :title="label.title" + :title="labelTitle(label)" :description="label.description" :scoped="scopedLabel(label)" + :target="labelTarget(label)" :class="{ 'gl-ml-2': index }" size="sm" /> </div> </div> <div class="issuable-meta"> + <ul v-if="hasSlotContents('status') || issuable.assignees" class="controls"> + <li v-if="hasSlotContents('status')" class="issuable-status"> + <slot name="status"></slot> + </li> + <li v-if="assignees.length" class="gl-display-flex"> + <issuable-assignees + :assignees="issuable.assignees" + :icon-size="16" + :max-visible="4" + img-css-classes="gl-mr-2!" + class="gl-align-items-center gl-display-flex gl-ml-3" + /> + </li> + </ul> <div data-testid="issuable-updated-at" class="float-right issuable-updated-at d-none d-sm-inline-block" diff --git a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue index 7535203dea1..52ebb9acdc3 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_list_root.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_list_root.vue @@ -1,6 +1,7 @@ <script> import { GlLoadingIcon, GlPagination } from '@gitlab/ui'; +import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import IssuableTabs from './issuable_tabs.vue'; @@ -35,6 +36,11 @@ export default { type: Array, required: true, }, + urlParams: { + type: Object, + required: false, + default: () => ({}), + }, initialFilterValue: { type: Array, required: false, @@ -55,7 +61,8 @@ export default { }, tabCounts: { type: Object, - required: true, + required: false, + default: null, }, currentTab: { type: String, @@ -81,6 +88,11 @@ export default { required: false, default: 20, }, + totalPages: { + type: Number, + required: false, + default: 0, + }, currentPage: { type: Number, required: false, @@ -96,6 +108,26 @@ export default { required: false, default: 2, }, + enableLabelPermalinks: { + type: Boolean, + required: false, + default: true, + }, + }, + watch: { + urlParams: { + deep: true, + immediate: true, + handler(params) { + if (Object.keys(params).length) { + updateHistory({ + url: setUrlParams(params, window.location.href, true), + title: document.title, + replace: true, + }); + } + }, + }, }, }; </script> @@ -135,12 +167,21 @@ export default { :key="issuable.id" :issuable-symbol="issuableSymbol" :issuable="issuable" - /> + :enable-label-permalinks="enableLabelPermalinks" + > + <template #reference> + <slot name="reference" :issuable="issuable"></slot> + </template> + <template #status> + <slot name="status" :issuable="issuable"></slot> + </template> + </issuable-item> </ul> <slot v-if="!issuablesLoading && !issuables.length" name="empty-state"></slot> <gl-pagination v-if="showPaginationControls" :per-page="defaultPageSize" + :total-items="totalPages" :value="currentPage" :prev-page="previousPage" :next-page="nextPage" diff --git a/app/assets/javascripts/issuable_list/components/issuable_tabs.vue b/app/assets/javascripts/issuable_list/components/issuable_tabs.vue index df544ce69e7..d9aab004077 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_tabs.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_tabs.vue @@ -14,7 +14,8 @@ export default { }, tabCounts: { type: Object, - required: true, + required: false, + default: null, }, currentTab: { type: String, @@ -40,7 +41,7 @@ export default { > <template #title> <span :title="tab.titleTooltip">{{ tab.title }}</span> - <gl-badge variant="neutral" size="sm" class="gl-px-2 gl-py-1!">{{ + <gl-badge v-if="tabCounts" variant="neutral" size="sm" class="gl-px-2 gl-py-1!">{{ tabCounts[tab.name] }}</gl-badge> </template> diff --git a/app/assets/javascripts/issuable_list/constants.js b/app/assets/javascripts/issuable_list/constants.js new file mode 100644 index 00000000000..4d3ce61366a --- /dev/null +++ b/app/assets/javascripts/issuable_list/constants.js @@ -0,0 +1,49 @@ +import { __ } from '~/locale'; + +export const IssuableStates = { + Opened: 'opened', + Closed: 'closed', + All: 'all', +}; + +export const IssuableListTabs = [ + { + id: 'state-opened', + name: IssuableStates.Opened, + title: __('Open'), + titleTooltip: __('Filter by issues that are currently opened.'), + }, + { + id: 'state-closed', + name: IssuableStates.Closed, + title: __('Closed'), + titleTooltip: __('Filter by issues that are currently closed.'), + }, + { + id: 'state-all', + name: IssuableStates.All, + title: __('All'), + titleTooltip: __('Show all issues.'), + }, +]; + +export const AvailableSortOptions = [ + { + id: 1, + title: __('Created date'), + sortDirection: { + descending: 'created_desc', + ascending: 'created_asc', + }, + }, + { + id: 2, + title: __('Last updated'), + sortDirection: { + descending: 'updated_desc', + ascending: 'updated_asc', + }, + }, +]; + +export const DEFAULT_PAGE_SIZE = 20; diff --git a/app/assets/javascripts/notes/components/note_attachment.vue b/app/assets/javascripts/notes/components/note_attachment.vue index 72f9a4c7e74..b20facc4032 100644 --- a/app/assets/javascripts/notes/components/note_attachment.vue +++ b/app/assets/javascripts/notes/components/note_attachment.vue @@ -1,6 +1,11 @@ <script> +import { GlIcon } from '@gitlab/ui'; + export default { name: 'NoteAttachment', + components: { + GlIcon, + }, props: { attachment: { type: Object, @@ -29,7 +34,7 @@ export default { target="_blank" rel="noopener noreferrer" > - <i class="fa fa-paperclip" aria-hidden="true"> </i> {{ attachment.filename }} + <gl-icon name="paperclip" /> {{ attachment.filename }} </a> </div> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue index 97595e5d2ce..e52afe08336 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_actions.vue @@ -87,7 +87,7 @@ export default { :aria-label="__('Run manual or delayed jobs')" > <gl-icon name="play" class="icon-play" /> - <i class="fa fa-caret-down" aria-hidden="true"></i> + <gl-icon name="chevron-down" /> <gl-loading-icon v-if="isLoading" /> </button> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue index 4a3d134685e..55c71e299be 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue @@ -29,7 +29,7 @@ export default { :aria-label="__('Artifacts')" > <gl-icon name="download" /> - <i class="fa fa-caret-down" aria-hidden="true"></i> + <gl-icon name="chevron-down" /> </button> <ul class="dropdown-menu dropdown-menu-right"> <li v-for="(artifact, i) in artifacts" :key="i"> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue index 9d72bf4394e..7b67c34ded6 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue @@ -96,7 +96,12 @@ export default { </script> <template> - <div v-gl-tooltip:body.viewport.left :title="tooltipText" class="sidebar-collapsed-icon"> + <div + v-gl-tooltip:body.viewport.left + :title="tooltipText" + data-testid="collapsedState" + class="sidebar-collapsed-icon" + > <gl-icon name="timer" /> <div class="time-tracking-collapsed-summary"> <div :class="divClass"> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue index d4cc98e3743..99302993b9a 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue @@ -70,14 +70,19 @@ export default { </script> <template> - <div class="time-tracking-comparison-pane"> + <div data-testid="timeTrackingComparisonPane"> <div v-gl-tooltip + data-testid="compareMeter" :title="timeRemainingTooltip" :class="timeRemainingStatusClass" class="compare-meter" > - <gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" /> + <gl-progress-bar + data-testid="timeRemainingProgress" + :value="timeRemainingPercent" + :variant="progressBarVariant" + /> <div class="compare-display-container"> <div class="compare-display float-left"> <span class="compare-label">{{ s__('TimeTracking|Spent') }}</span> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue index 305726d9725..8a80b1bf13f 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/estimate_only_pane.vue @@ -11,7 +11,8 @@ export default { </script> <template> - <div class="time-tracking-estimate-only-pane"> - <span class="bold"> {{ s__('TimeTracking|Estimated:') }} </span> {{ timeEstimateHumanReadable }} + <div data-testid="estimateOnlyPane"> + <span class="gl-font-weight-bold">{{ s__('TimeTracking|Estimated:') }} </span + >{{ timeEstimateHumanReadable }} </div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue index b45746e789d..8bc828091c0 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue @@ -34,7 +34,7 @@ export default { </script> <template> - <div class="time-tracking-help-state"> + <div data-testid="helpPane" class="time-tracking-help-state"> <div class="time-tracking-info"> <h4>{{ __('Track time with quick actions') }}</h4> <p>{{ __('Quick actions can be used in the issues description and comment boxes.') }}</p> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue index 45552589e50..2d3d0ce8dc5 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/no_tracking_pane.vue @@ -5,7 +5,7 @@ export default { </script> <template> - <div class="time-tracking-no-tracking-pane"> - <span class="no-value"> {{ __('No estimate or time spent') }} </span> + <div data-testid="noTrackingPane"> + <span class="no-value">{{ __('No estimate or time spent') }}</span> </div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue index b2b3b289c5c..33c6ac6e2ba 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue @@ -15,7 +15,7 @@ export default { return sprintf( s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'), { - startTag: '<span class="bold">', + startTag: '<span class="gl-font-weight-bold">', endTag: '</span>', timeSpentHumanReadable: this.timeSpentHumanReadable, }, @@ -27,5 +27,5 @@ export default { </script> <template> - <div class="time-tracking-spend-only-pane" v-html="timeSpent"></div> + <div data-testid="spentOnlyPane" v-html="timeSpent"></div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index a2fb0ebcbc6..95864462a68 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -105,11 +105,17 @@ export default { /> <div class="title hide-collapsed"> {{ __('Time tracking') }} - <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)"> + <div + v-if="!showHelpState" + data-testid="helpButton" + class="help-button float-right" + @click="toggleHelpState(true)" + > <gl-icon name="question-o" /> </div> <div - v-if="showHelpState" + v-else + data-testid="closeHelpButton" class="close-help-button float-right" @click="toggleHelpState(false)" > diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js index c1521882682..0a1211d0a76 100644 --- a/app/assets/javascripts/tracking.js +++ b/app/assets/javascripts/tracking.js @@ -12,6 +12,7 @@ const DEFAULT_SNOWPLOW_OPTIONS = { contexts: { webPage: true, performanceTiming: true }, formTracking: false, linkClickTracking: false, + pageUnloadTimer: 10, }; const createEventPayload = (el, { suffix = '' } = {}) => { diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss index a31cb0b0485..ad4d42abae6 100644 --- a/app/assets/stylesheets/_page_specific_files.scss +++ b/app/assets/stylesheets/_page_specific_files.scss @@ -39,7 +39,6 @@ @import './pages/settings'; @import './pages/settings_ci_cd'; @import './pages/sherlock'; -@import './pages/status'; @import './pages/storage_quota'; @import './pages/tree'; @import './pages/trials'; diff --git a/app/assets/stylesheets/fontawesome_custom.scss b/app/assets/stylesheets/fontawesome_custom.scss index 7b1953be69d..937ed5119cb 100644 --- a/app/assets/stylesheets/fontawesome_custom.scss +++ b/app/assets/stylesheets/fontawesome_custom.scss @@ -117,10 +117,6 @@ content: '\f077'; } -.fa-paperclip::before { - content: '\f0c6'; -} - .fa-bug::before { content: '\f188'; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/page_bundles/ci_status.scss index b37c5172ad2..8522a0a8fe4 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/page_bundles/ci_status.scss @@ -1,3 +1,5 @@ +@import 'mixins_and_variables_and_functions'; + .ci-status { padding: 2px 7px 4px; border: 1px solid $gray-darker; diff --git a/app/assets/stylesheets/page_bundles/todos.scss b/app/assets/stylesheets/page_bundles/todos.scss index 3eec5b53a30..3e20ca9c62f 100644 --- a/app/assets/stylesheets/page_bundles/todos.scss +++ b/app/assets/stylesheets/page_bundles/todos.scss @@ -219,7 +219,6 @@ .todos-empty-content { align-self: center; max-width: 480px; - margin-right: 20px; } .todos-empty-hero { |