diff options
Diffstat (limited to 'app/assets/javascripts')
47 files changed, 401 insertions, 234 deletions
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index bb3b2865934..669630edcab 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -30,6 +30,7 @@ class ListIssue { this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; this.milestone_id = obj.milestone_id; this.project_id = obj.project_id; + this.assignableLabelsEndpoint = obj.assignable_labels_endpoint; if (obj.project) { this.project = new IssueProject(obj.project); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index a2aa3d197e3..82532539c9c 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -2,9 +2,15 @@ import PipelinesService from '../../pipelines/services/pipelines_service'; import PipelineStore from '../../pipelines/stores/pipelines_store'; import pipelinesMixin from '../../pipelines/mixins/pipelines'; +import TablePagination from '../../vue_shared/components/table_pagination.vue'; +import { getParameterByName } from '../../lib/utils/common_utils'; +import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; export default { - mixins: [pipelinesMixin], + components: { + TablePagination, + }, + mixins: [pipelinesMixin, CIPaginationMixin], props: { endpoint: { type: String, @@ -35,6 +41,8 @@ export default { return { store, state: store.state, + page: getParameterByName('page') || '1', + requestData: {}, }; }, @@ -48,11 +56,14 @@ export default { }, created() { this.service = new PipelinesService(this.endpoint); + this.requestData = { page: this.page }; }, methods: { successCallback(resp) { // depending of the endpoint the response can either bring a `pipelines` key or not. const pipelines = resp.data.pipelines || resp.data; + + this.store.storePagination(resp.headers); this.setCommonData(pipelines); const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { @@ -97,5 +108,11 @@ export default { :view-type="viewType" /> </div> + + <table-pagination + v-if="shouldRenderPagination" + :change="onChangePage" + :page-info="state.pageInfo" + /> </div> </template> diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js index e93e1f5ea2c..6c18a0fd390 100644 --- a/app/assets/javascripts/commons/gitlab_ui.js +++ b/app/assets/javascripts/commons/gitlab_ui.js @@ -1,7 +1,4 @@ import Vue from 'vue'; -import { GlProgressBar, GlLoadingIcon, GlTooltipDirective } from '@gitlab-org/gitlab-ui'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; -Vue.component('gl-progress-bar', GlProgressBar); Vue.component('gl-loading-icon', GlLoadingIcon); - -Vue.directive('gl-tooltip', GlTooltipDirective); diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue index 1b59777f901..254bc235691 100644 --- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue +++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue @@ -3,6 +3,7 @@ import { mapActions } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; import { pluralize, truncate } from '~/lib/utils/text_utility'; import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; +import { GlTooltipDirective } from '@gitlab-org/gitlab-ui'; import { COUNT_OF_AVATARS_IN_GUTTER, LENGTH_OF_AVATAR_TOOLTIP } from '../constants'; export default { @@ -10,6 +11,9 @@ export default { Icon, UserAvatarImage, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { discussions: { type: Array, diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue index 6eff3013dcd..f4a9be19496 100644 --- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue +++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue @@ -167,7 +167,7 @@ export default { <button v-if="shouldShowCommentButton" type="button" - class="add-diff-note js-add-diff-note-button" + class="add-diff-note js-add-diff-note-button qa-diff-comment" title="Add a comment to this line" @click="handleCommentButton" > diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue index 62fa34e835a..542acd3d930 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue @@ -102,7 +102,7 @@ export default { :line-type="newLineType" :is-bottom="isBottom" :is-hover="isHover" - class="diff-line-num new_line" + class="diff-line-num new_line qa-new-diff-line" /> <td :class="line.type" diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_form.js b/app/assets/javascripts/dirty_submit/dirty_submit_form.js index 5bea47f23c5..d8d0fa1fac4 100644 --- a/app/assets/javascripts/dirty_submit/dirty_submit_form.js +++ b/app/assets/javascripts/dirty_submit/dirty_submit_form.js @@ -31,7 +31,7 @@ class DirtySubmitForm { updateDirtyInput(event) { const input = event.target; - if (!input.dataset.dirtySubmitOriginalValue) return; + if (!input.dataset.isDirtySubmitInput) return; this.updateDirtyInputs(input); this.toggleSubmission(); @@ -65,6 +65,7 @@ class DirtySubmitForm { } static initInput(element) { + element.dataset.isDirtySubmitInput = true; element.dataset.dirtySubmitOriginalValue = DirtySubmitForm.inputCurrentValue(element); } diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index 2bc168a6b02..0a3ae384afa 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -1,4 +1,6 @@ <script> +import { s__, sprintf } from '~/locale'; +import { formatTime } from '~/lib/utils/datetime_utility'; import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '../event_hub'; import tooltip from '../../vue_shared/directives/tooltip'; @@ -28,10 +30,24 @@ export default { }, }, methods: { - onClickAction(endpoint) { + onClickAction(action) { + if (action.scheduledAt) { + const confirmationMessage = sprintf( + s__( + "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes.", + ), + { jobName: action.name }, + ); + // https://gitlab.com/gitlab-org/gitlab-ce/issues/52156 + // eslint-disable-next-line no-alert + if (!window.confirm(confirmationMessage)) { + return; + } + } + this.isLoading = true; - eventHub.$emit('postAction', { endpoint }); + eventHub.$emit('postAction', { endpoint: action.playPath }); }, isActionDisabled(action) { @@ -41,6 +57,11 @@ export default { return !action.playable; }, + + remainingTime(action) { + const remainingMilliseconds = new Date(action.scheduledAt).getTime() - Date.now(); + return formatTime(Math.max(0, remainingMilliseconds)); + }, }, }; </script> @@ -54,7 +75,7 @@ export default { :aria-label="title" :disabled="isLoading" type="button" - class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container" + class="dropdown btn btn-default dropdown-new js-environment-actions-dropdown" data-container="body" data-toggle="dropdown" > @@ -75,12 +96,19 @@ export default { :class="{ disabled: isActionDisabled(action) }" :disabled="isActionDisabled(action)" type="button" - class="js-manual-action-link no-btn btn" - @click="onClickAction(action.play_path)" + class="js-manual-action-link no-btn btn d-flex align-items-center" + @click="onClickAction(action)" > - <span> + <span class="flex-fill"> {{ action.name }} </span> + <span + v-if="action.scheduledAt" + class="text-secondary" + > + <icon name="clock" /> + {{ remainingTime(action) }} + </span> </button> </li> </ul> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index b62a5bb1940..41f59447905 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -13,6 +13,7 @@ import TerminalButtonComponent from './environment_terminal_button.vue'; import MonitoringButtonComponent from './environment_monitoring.vue'; import CommitComponent from '../../vue_shared/components/commit.vue'; import eventHub from '../event_hub'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; /** * Environment Item Component @@ -74,21 +75,6 @@ export default { }, /** - * Verifies is the given environment has manual actions. - * Used to verify if we should render them or nor. - * - * @returns {Boolean|Undefined} - */ - hasManualActions() { - return ( - this.model && - this.model.last_deployment && - this.model.last_deployment.manual_actions && - this.model.last_deployment.manual_actions.length > 0 - ); - }, - - /** * Checkes whether the environment is protected. * (`is_protected` currently only set in EE) * @@ -154,23 +140,20 @@ export default { return ''; }, - /** - * Returns the manual actions with the name parsed. - * - * @returns {Array.<Object>|Undefined} - */ - manualActions() { - if (this.hasManualActions) { - return this.model.last_deployment.manual_actions.map(action => { - const parsedAction = { - name: humanize(action.name), - play_path: action.play_path, - playable: action.playable, - }; - return parsedAction; - }); + actions() { + if (!this.model || !this.model.last_deployment || !this.canCreateDeployment) { + return []; } - return []; + + const { manualActions, scheduledActions } = convertObjectPropsToCamelCase( + this.model.last_deployment, + { deep: true }, + ); + const combinedActions = (manualActions || []).concat(scheduledActions || []); + return combinedActions.map(action => ({ + ...action, + name: humanize(action.name), + })); }, /** @@ -443,7 +426,7 @@ export default { displayEnvironmentActions() { return ( - this.hasManualActions || + this.actions.length > 0 || this.externalURL || this.monitoringUrl || this.canStopEnvironment || @@ -619,8 +602,8 @@ export default { /> <actions-component - v-if="hasManualActions && canCreateDeployment" - :actions="manualActions" + v-if="actions.length > 0" + :actions="actions" /> <terminal-button-component diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue index 17fd5321642..93c89411b4a 100644 --- a/app/assets/javascripts/jobs/components/artifacts_block.vue +++ b/app/assets/javascripts/jobs/components/artifacts_block.vue @@ -1,10 +1,12 @@ <script> +import { GlLink } from '@gitlab-org/gitlab-ui'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; export default { components: { TimeagoTooltip, + GlLink, }, mixins: [timeagoMixin], props: { @@ -53,16 +55,16 @@ export default { class="btn-group d-flex" role="group" > - <a + <gl-link v-if="artifact.keep_path" :href="artifact.keep_path" class="js-keep-artifacts btn btn-sm btn-default" data-method="post" > {{ s__('Job|Keep') }} - </a> + </gl-link> - <a + <gl-link v-if="artifact.download_path" :href="artifact.download_path" class="js-download-artifacts btn btn-sm btn-default" @@ -70,15 +72,15 @@ export default { rel="nofollow" > {{ s__('Job|Download') }} - </a> + </gl-link> - <a + <gl-link v-if="artifact.browse_path" :href="artifact.browse_path" class="js-browse-artifacts btn btn-sm btn-default" > {{ s__('Job|Browse') }} - </a> + </gl-link> </div> </div> </template> diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue index 7d51f6afd10..06fe23fedce 100644 --- a/app/assets/javascripts/jobs/components/commit_block.vue +++ b/app/assets/javascripts/jobs/components/commit_block.vue @@ -1,9 +1,11 @@ <script> +import { GlLink } from '@gitlab-org/gitlab-ui'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; export default { components: { ClipboardButton, + GlLink, }, props: { commit: { @@ -31,10 +33,10 @@ export default { <p> {{ __('Commit') }} - <a + <gl-link :href="commit.commit_path" class="js-commit-sha commit-sha link-commit" - >{{ commit.short_id }}</a> + >{{ commit.short_id }}</gl-link> <clipboard-button :text="commit.short_id" @@ -42,11 +44,11 @@ export default { css-class="btn btn-clipboard btn-transparent" /> - <a + <gl-link v-if="mergeRequest" :href="mergeRequest.path" class="js-link-commit link-commit" - >!{{ mergeRequest.iid }}</a> + >!{{ mergeRequest.iid }}</gl-link> </p> <p class="build-light-text append-bottom-0"> diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue index ee5ceb99b0a..be7425c2d25 100644 --- a/app/assets/javascripts/jobs/components/empty_state.vue +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -1,5 +1,10 @@ <script> +import { GlLink } from '@gitlab-org/gitlab-ui'; + export default { + components: { + GlLink, + }, props: { illustrationPath: { type: String, @@ -62,13 +67,13 @@ export default { v-if="action" class="text-center" > - <a + <gl-link :href="action.path" :data-method="action.method" class="js-job-empty-state-action btn btn-primary" > {{ action.button_title }} - </a> + </gl-link> </div> </div> </div> diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue index 5ffbfb6e19a..d80e905c68e 100644 --- a/app/assets/javascripts/jobs/components/erased_block.vue +++ b/app/assets/javascripts/jobs/components/erased_block.vue @@ -1,10 +1,12 @@ <script> import _ from 'underscore'; +import { GlLink } from '@gitlab-org/gitlab-ui'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; export default { components: { TimeagoTooltip, + GlLink, }, props: { user: { @@ -29,9 +31,9 @@ export default { <div class="erased alert alert-warning"> <template v-if="isErasedByUser"> {{ s__("Job|Job has been erased by") }} - <a :href="user.web_url"> + <gl-link :href="user.web_url"> {{ user.username }} - </a> + </gl-link> </template> <template v-else> {{ s__("Job|Job has been erased") }} diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index 3cabbfc6e27..6e95e3d16f8 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -1,6 +1,7 @@ <script> import _ from 'underscore'; import { mapGetters, mapState, mapActions } from 'vuex'; +import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; import bp from '~/breakpoints'; import CiHeader from '~/vue_shared/components/header_ci_component.vue'; @@ -23,6 +24,7 @@ export default { EmptyState, EnvironmentsBlock, ErasedBlock, + GlLoadingIcon, Log, LogTopBar, StuckBlock, diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue index 6486b25c8a7..cdac8a391d1 100644 --- a/app/assets/javascripts/jobs/components/job_container_item.vue +++ b/app/assets/javascripts/jobs/components/job_container_item.vue @@ -1,15 +1,16 @@ <script> +import { GlTooltipDirective, GlLink } from '@gitlab-org/gitlab-ui'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; -import tooltip from '~/vue_shared/directives/tooltip'; export default { components: { CiIcon, Icon, + GlLink, }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, props: { job: { @@ -37,11 +38,10 @@ export default { active: isActive }" > - <a - v-tooltip + <gl-link + v-gl-tooltip :href="job.status.details_path" :title="tooltipText" - data-container="body" data-boundary="viewport" class="js-job-link" > @@ -60,6 +60,6 @@ export default { name="retry" class="js-retry-icon" /> - </a> + </gl-link> </div> </template> diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue index 94ab1b16c84..eeefa33264f 100644 --- a/app/assets/javascripts/jobs/components/job_log_controllers.vue +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -1,7 +1,7 @@ <script> +import { GlTooltipDirective, GlLink, GlButton } from '@gitlab-org/gitlab-ui'; import { polyfillSticky } from '~/lib/utils/sticky'; import Icon from '~/vue_shared/components/icon.vue'; -import tooltip from '~/vue_shared/directives/tooltip'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { sprintf } from '~/locale'; import scrollDown from '../svg/scroll_down.svg'; @@ -9,9 +9,11 @@ import scrollDown from '../svg/scroll_down.svg'; export default { components: { Icon, + GlLink, + GlButton, }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, scrollDown, props: { @@ -73,76 +75,70 @@ export default { <template v-if="isTraceSizeVisible"> {{ jobLogSize }} - <a + <gl-link v-if="rawPath" :href="rawPath" class="js-raw-link raw-link" > {{ s__("Job|Complete Raw") }} - </a> + </gl-link> </template> </div> <!-- eo truncate information --> <div class="controllers float-right"> <!-- links --> - <a + <gl-link v-if="rawPath" - v-tooltip + v-gl-tooltip.body :title="s__('Job|Show complete raw')" :href="rawPath" class="js-raw-link-controller controllers-buttons" - data-container="body" > <icon name="doc-text" /> - </a> + </gl-link> - <a + <gl-link v-if="erasePath" - v-tooltip + v-gl-tooltip.body :title="s__('Job|Erase job log')" :href="erasePath" :data-confirm="__('Are you sure you want to erase this build?')" class="js-erase-link controllers-buttons" - data-container="body" data-method="post" > <icon name="remove" /> - </a> + </gl-link> <!-- eo links --> <!-- scroll buttons --> <div - v-tooltip + v-gl-tooltip :title="s__('Job|Scroll to top')" class="controllers-buttons" - data-container="body" > - <button + <gl-button :disabled="isScrollTopDisabled" type="button" class="js-scroll-top btn-scroll btn-transparent btn-blank" @click="handleScrollToTop" > - <icon name="scroll_up"/> - </button> + <icon name="scroll_up" /> + </gl-button> </div> <div - v-tooltip + v-gl-tooltip :title="s__('Job|Scroll to bottom')" class="controllers-buttons" - data-container="body" > - <button + <gl-button :disabled="isScrollBottomDisabled" - type="button" class="js-scroll-bottom btn-scroll btn-transparent btn-blank" :class="{ animate: isScrollingDown }" @click="handleScrollToBottom" v-html="$options.scrollDown" - > - </button> + /> </div> <!-- eo scroll buttons --> </div> diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue index aeafe98a70b..cfedb38e17a 100644 --- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue +++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue @@ -1,6 +1,11 @@ <script> +import { GlLink } from '@gitlab-org/gitlab-ui'; + export default { name: 'SidebarDetailRow', + components: { + GlLink, + }, props: { title: { type: String, @@ -41,7 +46,7 @@ export default { v-if="hasHelpURL" class="help-button float-right" > - <a + <gl-link :href="helpUrl" target="_blank" rel="noopener noreferrer nofollow" @@ -50,7 +55,7 @@ export default { class="fa fa-question-circle" aria-hidden="true" ></i> - </a> + </gl-link> </span> </p> </template> diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue index 1d5789b175a..ca4bf471363 100644 --- a/app/assets/javascripts/jobs/components/stuck_block.vue +++ b/app/assets/javascripts/jobs/components/stuck_block.vue @@ -1,8 +1,12 @@ <script> +import { GlLink } from '@gitlab-org/gitlab-ui'; /** * Renders Stuck Runners block for job's view. */ export default { + components: { + GlLink, + }, props: { hasNoRunnersForProject: { type: Boolean, @@ -52,12 +56,12 @@ export default { </p> {{ __("Go to") }} - <a + <gl-link v-if="runnersPath" :href="runnersPath" class="js-runners-path" > {{ __("Runners page") }} - </a> + </gl-link> </div> </template> diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 5457604b3b9..c0a76814102 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -59,7 +59,6 @@ export default class LabelsSelect { $toggleText = $dropdown.find('.dropdown-toggle-text'); namespacePath = $dropdown.data('namespacePath'); projectPath = $dropdown.data('projectPath'); - labelUrl = $dropdown.data('labels'); issueUpdateURL = $dropdown.data('issueUpdate'); selectedLabel = $dropdown.data('selected'); if (selectedLabel != null && !$dropdown.hasClass('js-multiselect')) { @@ -168,6 +167,7 @@ export default class LabelsSelect { $dropdown.glDropdown({ showMenuAbove: showMenuAbove, data: function(term, callback) { + labelUrl = $dropdown.attr('data-labels'); axios .get(labelUrl) .then(res => { diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index b980e43b898..554db102027 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -390,7 +390,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" :disabled="isSubmitButtonDisabled" name="button" type="button" - class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle" + class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle qa-note-dropdown" data-display="static" data-toggle="dropdown" aria-label="Open comment type dropdown"> @@ -422,7 +422,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" <li :class="{ 'droplab-item-selected': noteType === 'discussion' }"> <button type="button" - class="btn btn-transparent" + class="btn btn-transparent qa-discussion-option" @click.prevent="setNoteType('discussion')"> <i aria-hidden="true" diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index 6e8f43048d1..affa2d1b574 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -1,7 +1,8 @@ <script> import $ from 'jquery'; -import Icon from '~/vue_shared/components/icon.vue'; import { mapGetters, mapActions } from 'vuex'; +import Icon from '~/vue_shared/components/icon.vue'; +import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE } from '../constants'; export default { components: { @@ -12,14 +13,17 @@ export default { type: Array, required: true, }, - defaultValue: { + selectedValue: { type: Number, default: null, required: false, }, }, data() { - return { currentValue: this.defaultValue }; + return { + currentValue: this.selectedValue, + defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE, + }; }, computed: { ...mapGetters(['getNotesDataByProp']), @@ -28,8 +32,11 @@ export default { return this.filters.find(filter => filter.value === this.currentValue); }, }, + mounted() { + this.toggleCommentsForm(); + }, methods: { - ...mapActions(['filterDiscussion']), + ...mapActions(['filterDiscussion', 'setCommentsDisabled']), selectFilter(value) { const filter = parseInt(value, 10); @@ -39,6 +46,10 @@ export default { if (filter === this.currentValue) return; this.currentValue = filter; this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter }); + this.toggleCommentsForm(); + }, + toggleCommentsForm() { + this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE); }, }, }; @@ -73,6 +84,10 @@ export default { > {{ filter.title }} </button> + <div + v-if="filter.value === defaultValue" + class="dropdown-divider" + ></div> </li> </ul> </div> diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 38c43e5fe08..31ee8fed984 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -187,7 +187,7 @@ export default { :data-supports-quick-actions="!isEditing" name="note[note]" class="note-textarea js-gfm-input js-note-text -js-autosize markdown-area js-vue-issue-note-form js-vue-textarea" +js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input" aria-label="Description" placeholder="Write a comment or drag your files hereā¦" @keydown.meta.enter="handleUpdate()" diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index c5fdfa1d47c..6293dd5b7e1 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -369,7 +369,7 @@ Please check your network connection and try again.`; role="group"> <button type="button" - class="js-vue-discussion-reply btn btn-text-field mr-2" + class="js-vue-discussion-reply btn btn-text-field mr-2 qa-discussion-reply" title="Add a reply" @click="showReplyForm">Reply...</button> </div> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 7514ce8a1eb..ed5ac112dc0 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -60,6 +60,7 @@ export default { 'getNotesDataByProp', 'discussionCount', 'isLoading', + 'commentsDisabled', ]), noteableType() { return this.noteableData.noteableType; @@ -206,6 +207,7 @@ export default { </ul> <comment-form + v-if="!commentsDisabled" :noteable-type="noteableType" :markdown-version="markdownVersion" /> diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 2c3e07c0506..3147dc64c27 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -15,6 +15,8 @@ export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest'; export const UNRESOLVE_NOTE_METHOD_NAME = 'delete'; export const RESOLVE_NOTE_METHOD_NAME = 'post'; export const DESCRIPTION_TYPE = 'changed the description'; +export const HISTORY_ONLY_FILTER_VALUE = 2; +export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0; export const NOTEABLE_TYPE_MAPPING = { Issue: ISSUE_NOTEABLE_TYPE, diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js index 06eadaeea0e..5c5f38a3fb0 100644 --- a/app/assets/javascripts/notes/discussion_filters.js +++ b/app/assets/javascripts/notes/discussion_filters.js @@ -6,7 +6,7 @@ export default store => { if (discussionFilterEl) { const { defaultFilter, notesFilters } = discussionFilterEl.dataset; - const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null; + const selectedValue = defaultFilter ? parseInt(defaultFilter, 10) : null; const filterValues = notesFilters ? JSON.parse(notesFilters) : {}; const filters = Object.keys(filterValues).map(entry => ({ title: entry, @@ -24,7 +24,7 @@ export default store => { return createElement('discussion-filter', { props: { filters, - defaultValue, + selectedValue, }, }); }, diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index b5dd49bc6c9..88739ffb083 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -364,5 +364,9 @@ export const filterDiscussion = ({ dispatch }, { path, filter }) => { }); }; +export const setCommentsDisabled = ({ commit }, data) => { + commit(types.DISABLE_COMMENTS, data); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index e4f36154fcd..8df95c279eb 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -192,5 +192,7 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => { return getters.unresolvedDiscussionsIdsByDate[0]; }; +export const commentsDisabled = state => state.commentsDisabled; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 400142668ea..8aea269ea7d 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -21,6 +21,7 @@ export default () => ({ noteableData: { current_user: {}, }, + commentsDisabled: false, }, actions, getters, diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index 2fa53aef1d4..dfbf3b7b34b 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -15,6 +15,7 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES'; export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE'; export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE'; +export const DISABLE_COMMENTS = 'DISABLE_COMMENTS'; // DISCUSSION export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 65085452139..c8d9e196103 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -225,4 +225,8 @@ export default { discussion.truncated_diff_lines = diffLines; }, + + [types.DISABLE_COMMENTS](state, value) { + state.commentsDisabled = value; + }, }; diff --git a/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js b/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js deleted file mode 100644 index d4f34e32a48..00000000000 --- a/app/assets/javascripts/pages/projects/clusters/gcp/new/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; - -document.addEventListener('DOMContentLoaded', () => { - initGkeDropdowns(); -}); diff --git a/app/assets/javascripts/pages/projects/jobs/index/index.js b/app/assets/javascripts/pages/projects/jobs/index/index.js new file mode 100644 index 00000000000..1b57c67f16b --- /dev/null +++ b/app/assets/javascripts/pages/projects/jobs/index/index.js @@ -0,0 +1,16 @@ +import Vue from 'vue'; +import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; + +document.addEventListener('DOMContentLoaded', () => { + const remainingTimeElements = document.querySelectorAll('.js-remaining-time'); + remainingTimeElements.forEach( + el => + new Vue({ + ...GlCountdown, + el, + propsData: { + endDateString: el.dateTime, + }, + }), + ); +}); diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index ea526cf1309..fcd8a54c9c1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -155,14 +155,6 @@ export default { ); }, - shouldRenderPagination() { - return ( - !this.isLoading && - this.state.pipelines.length && - this.state.pageInfo.total > this.state.pageInfo.perPage - ); - }, - emptyTabMessage() { const { scopes } = this.$options; const possibleScopes = [scopes.pending, scopes.running, scopes.finished]; @@ -232,36 +224,6 @@ export default { this.setCommonData(resp.data.pipelines); } }, - /** - * Handles URL and query parameter changes. - * When the user uses the pagination or the tabs, - * - update URL - * - Make API request to the server with new parameters - * - Update the polling function - * - Update the internal state - */ - updateContent(parameters) { - this.updateInternalState(parameters); - - // fetch new data - return this.service - .getPipelines(this.requestData) - .then(response => { - this.isLoading = false; - this.successCallback(response); - - // restart polling - this.poll.restart({ data: this.requestData }); - }) - .catch(() => { - this.isLoading = false; - this.errorCallback(); - - // restart polling - this.poll.restart({ data: this.requestData }); - }); - }, - handleResetRunnersCache(endpoint) { this.isResetCacheButtonLoading = true; diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index a7507fb3b6f..07a4af3e61e 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -29,7 +29,7 @@ export default { if (action.scheduled_at) { const confirmationMessage = sprintf( s__( - "DelayedJobs|Are you sure you want to run %{jobName} immediately? This job will run automatically after it's timer finishes.", + "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes.", ), { jobName: action.name }, ); diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index 8929b397f6c..85781f548c6 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -23,6 +23,15 @@ export default { hasMadeRequest: false, }; }, + computed: { + shouldRenderPagination() { + return ( + !this.isLoading && + this.state.pipelines.length && + this.state.pageInfo.total > this.state.pageInfo.perPage + ); + }, + }, beforeMount() { this.poll = new Poll({ resource: this.service, @@ -65,6 +74,35 @@ export default { this.poll.stop(); }, methods: { + /** + * Handles URL and query parameter changes. + * When the user uses the pagination or the tabs, + * - update URL + * - Make API request to the server with new parameters + * - Update the polling function + * - Update the internal state + */ + updateContent(parameters) { + this.updateInternalState(parameters); + + // fetch new data + return this.service + .getPipelines(this.requestData) + .then(response => { + this.isLoading = false; + this.successCallback(response); + + // restart polling + this.poll.restart({ data: this.requestData }); + }) + .catch(() => { + this.isLoading = false; + this.errorCallback(); + + // restart polling + this.poll.restart({ data: this.requestData }); + }); + }, updateTable() { // Cancel ongoing request if (this.isMakingRequest) { diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue index 3b425ee2fed..f4243522ef8 100644 --- a/app/assets/javascripts/reports/components/issues_list.vue +++ b/app/assets/javascripts/reports/components/issues_list.vue @@ -1,18 +1,31 @@ <script> -import IssuesBlock from '~/reports/components/report_issues.vue'; -import { STATUS_SUCCESS, STATUS_FAILED, STATUS_NEUTRAL } from '~/reports/constants'; +import ReportItem from '~/reports/components/report_item.vue'; +import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants'; +import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; + +const wrapIssueWithState = (status, isNew = false) => issue => ({ + status: issue.status || status, + isNew, + issue, +}); /** * Renders block of issues */ - export default { components: { - IssuesBlock, + SmartVirtualList, + ReportItem, }, - success: STATUS_SUCCESS, - failed: STATUS_FAILED, - neutral: STATUS_NEUTRAL, + // Typical height of a report item in px + typicalReportItemHeight: 32, + /* + The maximum amount of shown issues. This is calculated by + ( max-height of report-block-list / typicalReportItemHeight ) + some safety margin + We will use VirtualList if we have more items than this number. + For entries lower than this number, the virtual scroll list calculates the total height of the element wrongly. + */ + maxShownReportItems: 20, props: { newIssues: { type: Array, @@ -40,42 +53,34 @@ export default { default: '', }, }, + computed: { + issuesWithState() { + return [ + ...this.newIssues.map(wrapIssueWithState(STATUS_FAILED, true)), + ...this.unresolvedIssues.map(wrapIssueWithState(STATUS_FAILED)), + ...this.neutralIssues.map(wrapIssueWithState(STATUS_NEUTRAL)), + ...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)), + ]; + }, + }, }; </script> <template> - <div class="report-block-container"> - - <issues-block - v-if="newIssues.length" - :component="component" - :issues="newIssues" - class="js-mr-code-new-issues" - status="failed" - is-new - /> - - <issues-block - v-if="unresolvedIssues.length" - :component="component" - :issues="unresolvedIssues" - :status="$options.failed" - class="js-mr-code-new-issues" - /> - - <issues-block - v-if="neutralIssues.length" - :component="component" - :issues="neutralIssues" - :status="$options.neutral" - class="js-mr-code-non-issues" - /> - - <issues-block - v-if="resolvedIssues.length" + <smart-virtual-list + :length="issuesWithState.length" + :remain="$options.maxShownReportItems" + :size="$options.typicalReportItemHeight" + class="report-block-container" + wtag="ul" + wclass="report-block-list" + > + <report-item + v-for="(wrapped, index) in issuesWithState" + :key="index" + :issue="wrapped.issue" + :status="wrapped.status" :component="component" - :issues="resolvedIssues" - :status="$options.success" - class="js-mr-code-resolved-issues" + :is-new="wrapped.isNew" /> - </div> + </smart-virtual-list> </template> diff --git a/app/assets/javascripts/reports/components/report_issues.vue b/app/assets/javascripts/reports/components/report_item.vue index a2a03945ae3..01e6d357a21 100644 --- a/app/assets/javascripts/reports/components/report_issues.vue +++ b/app/assets/javascripts/reports/components/report_item.vue @@ -3,14 +3,14 @@ import IssueStatusIcon from '~/reports/components/issue_status_icon.vue'; import { components, componentNames } from '~/reports/components/issue_body'; export default { - name: 'ReportIssues', + name: 'ReportItem', components: { IssueStatusIcon, ...components, }, props: { - issues: { - type: Array, + issue: { + type: Object, required: true, }, component: { @@ -33,27 +33,21 @@ export default { }; </script> <template> - <div> - <ul class="report-block-list"> - <li - v-for="(issue, index) in issues" - :key="index" - :class="{ 'is-dismissed': issue.isDismissed }" - class="report-block-list-issue" - > - <issue-status-icon - :status="issue.status || status" - class="append-right-5" - /> + <li + :class="{ 'is-dismissed': issue.isDismissed }" + class="report-block-list-issue" + > + <issue-status-icon + :status="status" + class="append-right-5" + /> - <component - :is="component" - v-if="component" - :issue="issue" - :status="issue.status || status" - :is-new="isNew" - /> - </li> - </ul> - </div> + <component + :is="component" + v-if="component" + :issue="issue" + :status="status" + :is-new="isNew" + /> + </li> </template> diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue index 8950ae31627..4d461baf74d 100644 --- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -5,7 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue'; import GfmAutoComplete from '~/gfm_auto_complete'; import { __, s__ } from '~/locale'; import Api from '~/api'; -import { GlModal } from '@gitlab-org/gitlab-ui'; +import { GlModal, GlTooltipDirective } from '@gitlab-org/gitlab-ui'; import eventHub from './event_hub'; import EmojiMenuInModal from './emoji_menu_in_modal'; @@ -16,6 +16,9 @@ export default { Icon, GlModal, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { currentEmoji: { type: String, 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 e74912d628f..b145e5dc5e2 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue @@ -1,9 +1,13 @@ <script> import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility'; import tooltip from '../../../vue_shared/directives/tooltip'; +import { GlProgressBar } from '@gitlab-org/gitlab-ui'; export default { name: 'TimeTrackingComparisonPane', + components: { + GlProgressBar, + }, directives: { tooltip, }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 57c52a2016a..2a8380f5f2b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -65,6 +65,14 @@ export default { deployedText() { return this.$options.deployedTextMap[this.deployment.status]; }, + isDeployInProgress() { + return this.deployment.status === 'running'; + }, + deployInProgressTooltip() { + return this.isDeployInProgress + ? __('Stopping this environment is currently not possible as a deployment is in progress') + : ''; + }, shouldRenderDropdown() { return ( this.enableCiEnvironmentsStatusChanges && @@ -183,15 +191,23 @@ export default { css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inlin" /> </template> - <loading-button + <span v-if="deployment.stop_url" - :loading="isStopping" - container-class="btn btn-default btn-sm inline prepend-left-4" - title="Stop environment" - @click="stopEnvironment" + v-tooltip + :title="deployInProgressTooltip" + class="d-inline-block" + tabindex="0" > - <icon name="stop" /> - </loading-button> + <loading-button + :loading="isStopping" + :disabled="isDeployInProgress" + :title="__('Stop environment')" + container-class="js-stop-env btn btn-default btn-sm inline prepend-left-4" + @click="stopEnvironment" + > + <icon name="stop" /> + </loading-button> + </span> </div> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 8bcabc10225..53608838f2f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -71,6 +71,7 @@ export default { linkStart: `<a href="${this.troubleshootingDocsPath}">`, linkEnd: '</a>', }, + false, ); }, }, diff --git a/app/assets/javascripts/vue_shared/components/gl_countdown.vue b/app/assets/javascripts/vue_shared/components/gl_countdown.vue index 9327a2a4a6c..a35986b2d03 100644 --- a/app/assets/javascripts/vue_shared/components/gl_countdown.vue +++ b/app/assets/javascripts/vue_shared/components/gl_countdown.vue @@ -1,10 +1,14 @@ <script> import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility'; +import { GlTooltipDirective } from '@gitlab-org/gitlab-ui'; /** * Counts down to a given end date. */ export default { + directives: { + GlTooltip: GlTooltipDirective, + }, props: { endDateString: { type: String, diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 3ddb39730c4..27e3f314dd3 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -1,17 +1,17 @@ <script> import $ from 'jquery'; -import Tooltip from '../../directives/tooltip'; +import { GlTooltipDirective } from '@gitlab-org/gitlab-ui'; import ToolbarButton from './toolbar_button.vue'; import Icon from '../icon.vue'; export default { - directives: { - Tooltip, - }, components: { ToolbarButton, Icon, }, + directives: { + GlTooltip: GlTooltipDirective, + }, props: { previewMarkdown: { type: Boolean, @@ -147,7 +147,7 @@ export default { icon="table" /> <button - v-tooltip + v-gl-tooltip aria-label="Go full screen" class="toolbar-btn toolbar-fullscreen-btn js-zen-enter" data-container="body" diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue index 3e89e1c1e75..91d0bbfc21c 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue @@ -1,13 +1,13 @@ <script> -import tooltip from '../../directives/tooltip'; -import icon from '../icon.vue'; +import { GlTooltipDirective } from '@gitlab-org/gitlab-ui'; +import Icon from '../icon.vue'; export default { components: { - icon, + Icon, }, directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, props: { buttonTitle: { @@ -43,7 +43,7 @@ export default { <template> <button - v-tooltip + v-gl-tooltip :data-md-tag="tag" :data-md-select="tagSelect" :data-md-block="tagBlock" diff --git a/app/assets/javascripts/vue_shared/components/smart_virtual_list.vue b/app/assets/javascripts/vue_shared/components/smart_virtual_list.vue new file mode 100644 index 00000000000..63034a45f77 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/smart_virtual_list.vue @@ -0,0 +1,42 @@ +<script> +import VirtualList from 'vue-virtual-scroll-list'; + +export default { + name: 'SmartVirtualList', + components: { VirtualList }, + props: { + size: { type: Number, required: true }, + length: { type: Number, required: true }, + remain: { type: Number, required: true }, + rtag: { type: String, default: 'div' }, + wtag: { type: String, default: 'div' }, + wclass: { type: String, default: null }, + }, +}; +</script> +<template> + <virtual-list + v-if="length > remain" + v-bind="$attrs" + :size="remain" + :remain="remain" + :rtag="rtag" + :wtag="wtag" + :wclass="wclass" + class="js-virtual-list" + > + <slot></slot> + </virtual-list> + <component + :is="rtag" + v-else + class="js-plain-element" + > + <component + :is="wtag" + :class="wclass" + > + <slot></slot> + </component> + </component> +</template> 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 67a1632269e..f9e3f3df0cc 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 @@ -14,7 +14,14 @@ export default { onChangePage(page) { /* URLS parameters are strings, we need to parse to match types */ - this.updateContent({ scope: this.scope, page: Number(page).toString() }); + const params = { + page: Number(page).toString(), + }; + + if (this.scope) { + params.scope = this.scope; + } + this.updateContent(params); }, updateInternalState(parameters) { |