diff options
Diffstat (limited to 'app/assets/javascripts/vue_merge_request_widget/components')
9 files changed, 279 insertions, 85 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue new file mode 100644 index 00000000000..eff26729fa7 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -0,0 +1,157 @@ +<script> +import { GlButton, GlLoadingIcon, GlIcon, GlLink, GlBadge, GlSafeHtmlDirective } from '@gitlab/ui'; +import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; +import StatusIcon from '../mr_widget_status_icon.vue'; + +export const LOADING_STATES = { + collapsedLoading: 'collapsedLoading', + collapsedError: 'collapsedError', + expandedLoading: 'expandedLoading', +}; + +export default { + components: { + GlButton, + GlLoadingIcon, + GlIcon, + GlLink, + GlBadge, + SmartVirtualList, + StatusIcon, + }, + directives: { + SafeHtml: GlSafeHtmlDirective, + }, + data() { + return { + loadingState: LOADING_STATES.collapsedLoading, + collapsedData: null, + fullData: null, + isCollapsed: true, + }; + }, + computed: { + isLoadingSummary() { + return this.loadingState === LOADING_STATES.collapsedLoading; + }, + isLoadingExpanded() { + return this.loadingState === LOADING_STATES.expandedLoading; + }, + isCollapsible() { + if (this.isLoadingSummary) { + return false; + } + + return true; + }, + statusIconName() { + if (this.isLoadingSummary) { + return 'loading'; + } + + if (this.loadingState === LOADING_STATES.collapsedError) { + return 'warning'; + } + + return this.statusIcon(this.collapsedData); + }, + }, + watch: { + isCollapsed(newVal) { + if (!newVal) { + this.loadAllData(); + } else { + this.loadingState = null; + } + }, + }, + mounted() { + this.fetchCollapsedData(this.$props) + .then(data => { + this.collapsedData = data; + this.loadingState = null; + }) + .catch(e => { + this.loadingState = LOADING_STATES.collapsedError; + throw e; + }); + }, + methods: { + toggleCollapsed() { + this.isCollapsed = !this.isCollapsed; + }, + loadAllData() { + if (this.fullData) return; + + this.loadingState = LOADING_STATES.expandedLoading; + + this.fetchFullData(this.$props) + .then(data => { + this.loadingState = null; + this.fullData = data; + }) + .catch(e => { + this.loadingState = null; + throw e; + }); + }, + }, +}; +</script> + +<template> + <section class="media-section mr-widget-border-top"> + <div class="media gl-p-5"> + <status-icon :status="statusIconName" class="align-self-center" /> + <div class="media-body d-flex flex-align-self-center align-items-center"> + <div class="code-text"> + <template v-if="isLoadingSummary"> + {{ __('Loading...') }} + </template> + <div v-else v-safe-html="summary(collapsedData)"></div> + </div> + <gl-button + v-if="isCollapsible" + size="small" + class="float-right align-self-center" + @click="toggleCollapsed" + > + {{ isCollapsed ? __('Expand') : __('Collapse') }} + </gl-button> + </div> + </div> + <div v-if="!isCollapsed" class="mr-widget-grouped-section"> + <div v-if="isLoadingExpanded" class="report-block-container"> + <gl-loading-icon inline /> {{ __('Loading...') }} + </div> + <smart-virtual-list + v-else-if="fullData" + :length="fullData.length" + :remain="20" + :size="32" + wtag="ul" + wclass="report-block-list" + class="report-block-container" + > + <li v-for="data in fullData" :key="data.id" class="d-flex align-items-center"> + <div v-if="data.icon" :class="data.icon.class" class="d-flex"> + <gl-icon :name="data.icon.name" :size="24" /> + </div> + <div + class="gl-mt-2 gl-mb-2 align-content-around align-items-start flex-wrap align-self-center d-flex" + > + <div class="gl-mr-4"> + {{ data.text }} + </div> + <div v-if="data.link"> + <gl-link :href="data.link.href">{{ data.link.text }}</gl-link> + </div> + <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'"> + {{ data.badge.text }} + </gl-badge> + </div> + </li> + </smart-virtual-list> + </div> + </section> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js new file mode 100644 index 00000000000..5014c12dc30 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js @@ -0,0 +1,27 @@ +import { extensions } from './index'; + +export default { + props: { + mr: { + type: Object, + required: true, + }, + }, + render(h) { + return h( + 'div', + {}, + extensions.map(extension => + h(extension, { + props: extensions[0].props.reduce( + (acc, key) => ({ + ...acc, + [key]: this.mr[key], + }), + {}, + ), + }), + ), + ); + }, +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js new file mode 100644 index 00000000000..2bfaec8a1c9 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js @@ -0,0 +1,30 @@ +import ExtensionBase from './base.vue'; + +// Holds all the currently registered extensions +export const extensions = []; + +export const registerExtension = extension => { + // Pushes into the extenions array a dynamically created Vue component + // that gets exteneded from `base.vue` + extensions.push({ + extends: ExtensionBase, + name: extension.name, + props: extension.props, + computed: { + ...Object.keys(extension.computed).reduce( + (acc, computedKey) => ({ + ...acc, + // Making the computed property a method allows us to pass in arguments + // this allows for each computed property to recieve some data + [computedKey]() { + return extension.computed[computedKey]; + }, + }), + {}, + ), + }, + methods: { + ...extension.methods, + }, + }); +}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue index 598b08f4c16..5ed699acddf 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue @@ -1,10 +1,10 @@ <script> -import tooltip from '../../vue_shared/directives/tooltip'; +import { GlTooltipDirective } from '@gitlab/ui'; export default { name: 'MrWidgetAuthor', directives: { - tooltip, + GlTooltip: GlTooltipDirective, }, props: { author: { @@ -16,11 +16,6 @@ export default { required: false, default: true, }, - showAuthorTooltip: { - type: Boolean, - required: false, - default: false, - }, }, computed: { authorUrl() { @@ -33,12 +28,7 @@ export default { }; </script> <template> - <a - :href="authorUrl" - :v-tooltip="showAuthorTooltip" - :title="author.name" - class="author-link inline" - > + <a v-gl-tooltip :href="authorUrl" :title="author.name" class="author-link inline"> <img :src="avatarUrl" class="avatar avatar-inline s16" /> <span v-if="showAuthorName" class="author">{{ author.name }}</span> </a> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index eb8989adb2a..d5fdbe726e9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -1,6 +1,5 @@ <script> /* eslint-disable vue/no-v-html */ -import Mousetrap from 'mousetrap'; import { escape } from 'lodash'; import { GlButton, @@ -84,17 +83,6 @@ export default { : ''; }, }, - mounted() { - Mousetrap.bind('b', this.copyBranchName); - }, - beforeDestroy() { - Mousetrap.unbind('b'); - }, - methods: { - copyBranchName() { - this.$refs.copyBranchNameButton.$el.click(); - }, - }, }; </script> <template> @@ -110,7 +98,6 @@ export default { class="label-branch label-truncate js-source-branch" v-html="mr.sourceBranchLink" /><clipboard-button - ref="copyBranchNameButton" data-testid="mr-widget-copy-clipboard" :text="branchNameClipboardData" :title="__('Copy branch name')" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue index f17e409d996..b6722de5277 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue @@ -1,10 +1,10 @@ <script> -import { GlDeprecatedDropdown, GlDeprecatedDropdownItem } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; export default { components: { - GlDeprecatedDropdown, - GlDeprecatedDropdownItem, + GlDropdown, + GlDropdownItem, }, props: { commits: { @@ -18,20 +18,20 @@ export default { <template> <div> - <gl-deprecated-dropdown + <gl-dropdown right text="Use an existing commit message" variant="link" class="mr-commit-dropdown" > - <gl-deprecated-dropdown-item + <gl-dropdown-item v-for="commit in commits" :key="commit.short_id" class="text-nowrap text-truncate" @click="$emit('input', commit.message)" > <span class="monospace mr-2">{{ commit.short_id }}</span> {{ commit.title }} - </gl-deprecated-dropdown-item> - </gl-deprecated-dropdown> + </gl-dropdown-item> + </gl-dropdown> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue index 12f65a4c235..750014c599a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue @@ -1,4 +1,5 @@ <script> +import { GlLoadingIcon } from '@gitlab/ui'; import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge'; import { deprecatedCreateFlash as Flash } from '../../../flash'; import statusIcon from '../mr_widget_status_icon.vue'; @@ -12,6 +13,7 @@ export default { components: { MrWidgetAuthor, statusIcon, + GlLoadingIcon, }, mixins: [autoMergeMixin], props: { @@ -100,7 +102,7 @@ export default { class="btn btn-sm btn-default js-cancel-auto-merge" @click.prevent="cancelAutomaticMerge" > - <i v-if="isCancellingAutoMerge" class="fa fa-spinner fa-spin" aria-hidden="true"> </i> + <gl-loading-icon v-if="isCancellingAutoMerge" inline class="gl-mr-1" /> {{ cancelButtonText }} </a> </h4> @@ -122,7 +124,7 @@ export default { href="#" @click.prevent="removeSourceBranch" > - <i v-if="isRemovingSourceBranch" class="fa fa-spinner fa-spin" aria-hidden="true"> </i> + <gl-loading-icon v-if="isRemovingSourceBranch" inline class="gl-mr-1" /> {{ s__('mrWidget|Delete source branch') }} </a> </p> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 835f7b9e9a9..2c1f2285dda 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -1,6 +1,15 @@ <script> import { isEmpty } from 'lodash'; -import { GlIcon, GlButton, GlSprintf, GlLink } from '@gitlab/ui'; +import { + GlIcon, + GlButton, + GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlSprintf, + GlLink, + GlTooltipDirective, +} from '@gitlab/ui'; import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge'; import simplePoll from '~/lib/utils/simple_poll'; import { __ } from '~/locale'; @@ -36,6 +45,9 @@ export default { GlSprintf, GlLink, GlButton, + GlButtonGroup, + GlDropdown, + GlDropdownItem, MergeTrainHelperText: () => import('ee_component/vue_merge_request_widget/components/merge_train_helper_text.vue'), MergeImmediatelyConfirmationDialog: () => @@ -43,6 +55,9 @@ export default { 'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue' ), }, + directives: { + GlTooltip: GlTooltipDirective, + }, mixins: [readyToMergeMixin], props: { mr: { type: Object, required: true }, @@ -283,7 +298,7 @@ export default { <status-icon :status="iconClass" /> <div class="media-body"> <div class="mr-widget-body-controls media space-children"> - <span class="btn-group"> + <gl-button-group> <gl-button size="medium" category="primary" @@ -294,54 +309,33 @@ export default { @click="handleMergeButtonClick(isAutoMergeAvailable)" >{{ mergeButtonText }}</gl-button > - <button + <gl-dropdown v-if="shouldShowMergeImmediatelyDropdown" + v-gl-tooltip.hover.focus="__('Select merge moment')" :disabled="isMergeButtonDisabled" - type="button" - class="btn btn-sm btn-info dropdown-toggle js-merge-moment" - data-toggle="dropdown" + variant="info" data-qa-selector="merge_moment_dropdown" - :aria-label="__('Select merge moment')" - > - <i class="fa fa-chevron-down" aria-hidden="true"></i> - </button> - <ul - v-if="shouldShowMergeImmediatelyDropdown" - class="dropdown-menu dropdown-menu-right" - role="menu" + toggle-class="btn-icon js-merge-moment" > - <li> - <a - class="auto_merge_enabled qa-merge-when-pipeline-succeeds-option" - href="#" - @click.prevent="handleMergeButtonClick(true)" - > - <span class="media"> - <gl-icon name="status_success" class="merge-opt-icon" aria-hidden="true" /> - <span class="media-body merge-opt-title">{{ autoMergeText }}</span> - </span> - </a> - </li> - <li> - <merge-immediately-confirmation-dialog - ref="confirmationDialog" - :docs-url="mr.mergeImmediatelyDocsPath" - @mergeImmediately="onMergeImmediatelyConfirmation" - /> - <a - class="accept-merge-request js-merge-immediately-button" - data-qa-selector="merge_immediately_option" - href="#" - @click.prevent="handleMergeImmediatelyButtonClick" - > - <span class="media"> - <gl-icon name="status_warning" class="merge-opt-icon" aria-hidden="true" /> - <span class="media-body merge-opt-title">{{ __('Merge immediately') }}</span> - </span> - </a> - </li> - </ul> - </span> + <template #button-content> + <gl-icon name="chevron-down" class="mr-0" /> + <span class="sr-only">{{ __('Select merge moment') }}</span> + </template> + <gl-dropdown-item + icon-name="warning" + button-class="accept-merge-request js-merge-immediately-button" + data-qa-selector="merge_immediately_option" + @click="handleMergeImmediatelyButtonClick" + > + {{ __('Merge immediately') }} + </gl-dropdown-item> + <merge-immediately-confirmation-dialog + ref="confirmationDialog" + :docs-url="mr.mergeImmediatelyDocsPath" + @mergeImmediately="onMergeImmediatelyConfirmation" + /> + </gl-dropdown> + </gl-button-group> <div class="media-body-wrap space-children"> <template v-if="shouldShowMergeControls"> <label v-if="mr.canRemoveSourceBranch"> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue index eba3d50fdc9..1d591168a17 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue @@ -1,6 +1,7 @@ <script> import $ from 'jquery'; import { GlButton } from '@gitlab/ui'; +import { produce } from 'immer'; import { __ } from '~/locale'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import MergeRequest from '~/merge_request'; @@ -80,12 +81,18 @@ export default { return; } - const data = store.readQuery({ + const sourceData = store.readQuery({ query: getStateQuery, variables: mergeRequestQueryVariables, }); - data.project.mergeRequest.workInProgress = workInProgress; - data.project.mergeRequest.title = title; + + const data = produce(sourceData, draftState => { + // eslint-disable-next-line no-param-reassign + draftState.project.mergeRequest.workInProgress = workInProgress; + // eslint-disable-next-line no-param-reassign + draftState.project.mergeRequest.title = title; + }); + store.writeQuery({ query: getStateQuery, data, @@ -143,7 +150,7 @@ export default { <div class="media-body"> <div class="gl-ml-3 float-left"> <span class="gl-font-weight-bold"> - {{ __('This merge request is still a work in progress.') }} + {{ __('This merge request is still a draft.') }} </span> <span class="gl-display-block text-muted">{{ __("Draft merge requests can't be merged.") |