Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-06-20 14:10:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-06-20 14:10:13 +0300
commit0ea3fcec397b69815975647f5e2aa5fe944a8486 (patch)
tree7979381b89d26011bcf9bdc989a40fcc2f1ed4ff /app/assets/javascripts/issuable
parent72123183a20411a36d607d70b12d57c484394c8e (diff)
Add latest changes from gitlab-org/gitlab@15-1-stable-eev15.1.0-rc42
Diffstat (limited to 'app/assets/javascripts/issuable')
-rw-r--r--app/assets/javascripts/issuable/components/csv_export_modal.vue34
-rw-r--r--app/assets/javascripts/issuable/components/csv_import_modal.vue4
-rw-r--r--app/assets/javascripts/issuable/components/issuable_header_warnings.vue2
-rw-r--r--app/assets/javascripts/issuable/components/related_issuable_item.vue5
-rw-r--r--app/assets/javascripts/issuable/components/status_box.vue5
-rw-r--r--app/assets/javascripts/issuable/issuable_form.js3
-rw-r--r--app/assets/javascripts/issuable/popover/components/issue_popover.vue83
-rw-r--r--app/assets/javascripts/issuable/popover/components/mr_popover.vue116
-rw-r--r--app/assets/javascripts/issuable/popover/constants.js13
-rw-r--r--app/assets/javascripts/issuable/popover/index.js85
-rw-r--r--app/assets/javascripts/issuable/popover/queries/issue.query.graphql11
-rw-r--r--app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql19
12 files changed, 360 insertions, 20 deletions
diff --git a/app/assets/javascripts/issuable/components/csv_export_modal.vue b/app/assets/javascripts/issuable/components/csv_export_modal.vue
index b0af3612e05..736da92fa9f 100644
--- a/app/assets/javascripts/issuable/components/csv_export_modal.vue
+++ b/app/assets/javascripts/issuable/components/csv_export_modal.vue
@@ -1,16 +1,18 @@
<script>
-import { GlButton, GlModal, GlSprintf, GlIcon } from '@gitlab/ui';
+import { GlModal, GlSprintf, GlIcon } from '@gitlab/ui';
import { __, n__ } from '~/locale';
import { ISSUABLE_TYPE } from '../constants';
export default {
+ actionCancel: {
+ text: __('Cancel'),
+ },
i18n: {
exportText: __(
'The CSV export will be created in the background. Once finished, it will be sent to %{email} in an attachment.',
),
},
components: {
- GlButton,
GlModal,
GlSprintf,
GlIcon,
@@ -38,6 +40,19 @@ export default {
},
},
computed: {
+ actionPrimary() {
+ return {
+ text: this.exportText,
+ attributes: {
+ href: this.exportCsvPath,
+ variant: 'confirm',
+ 'data-method': 'post',
+ 'data-qa-selector': `export_${this.issuableType}_button`,
+ 'data-track-action': 'click_button',
+ 'data-track-label': `export_${this.issuableType}_csv`,
+ },
+ };
+ },
isIssue() {
return this.issuableType === ISSUABLE_TYPE.issues;
},
@@ -56,6 +71,8 @@ export default {
<template>
<gl-modal
:modal-id="modalId"
+ :action-primary="actionPrimary"
+ :action-cancel="$options.actionCancel"
body-class="gl-p-0!"
:title="exportText"
data-qa-selector="export_issuable_modal"
@@ -73,18 +90,5 @@ export default {
</template>
</gl-sprintf>
</div>
- <template #modal-footer>
- <gl-button
- category="primary"
- variant="confirm"
- :href="exportCsvPath"
- data-method="post"
- :data-qa-selector="`export_${issuableType}_button`"
- data-track-action="click_button"
- :data-track-label="`export_${issuableType}_csv`"
- >
- {{ exportText }}
- </gl-button>
- </template>
</gl-modal>
</template>
diff --git a/app/assets/javascripts/issuable/components/csv_import_modal.vue b/app/assets/javascripts/issuable/components/csv_import_modal.vue
index 7e2cbf03801..72293343c48 100644
--- a/app/assets/javascripts/issuable/components/csv_import_modal.vue
+++ b/app/assets/javascripts/issuable/components/csv_import_modal.vue
@@ -18,6 +18,9 @@ export default {
actionPrimary: {
text: __('Import issues'),
},
+ actionCancel: {
+ text: __('Cancel'),
+ },
components: {
GlModal,
GlFormGroup,
@@ -55,6 +58,7 @@ export default {
:modal-id="modalId"
:title="$options.i18n.importIssuesText"
:action-primary="$options.actionPrimary"
+ :action-cancel="$options.actionCancel"
@primary="submitForm"
>
<form ref="form" :action="importCsvIssuesPath" enctype="multipart/form-data" method="post">
diff --git a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
index 06d1a2ee233..543dca0afe1 100644
--- a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
+++ b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue
@@ -27,7 +27,7 @@ export default {
return this.getNoteableData.confidential;
},
isMergeRequest() {
- return this.getNoteableData.targetType === 'merge_request' && this.glFeatures.updatedMrHeader;
+ return this.getNoteableData.targetType === 'merge_request';
},
warningIconsMeta() {
return [
diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue
index dfe18567608..e6379b35f7a 100644
--- a/app/assets/javascripts/issuable/components/related_issuable_item.vue
+++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue
@@ -71,8 +71,9 @@ export default {
:class="{
'issuable-info-container': !canReorder,
'card-body': canReorder,
+ 'gl-pr-2': canRemove,
}"
- class="item-body d-flex align-items-center py-2 px-3"
+ class="item-body d-flex align-items-center gl-py-3 gl-px-5"
>
<div
class="item-contents gl-display-flex gl-align-items-center gl-flex-wrap gl-flex-grow-1 flex-xl-nowrap gl-min-h-7"
@@ -170,7 +171,7 @@ export default {
<issue-assignees
v-if="assignees.length !== 0"
:assignees="assignees"
- class="item-assignees d-flex align-items-center align-self-end flex-shrink-0 d-md-none ml-2"
+ class="item-assignees d-flex align-items-center align-self-end flex-shrink-0 d-md-none gl-ml-3"
/>
</div>
</div>
diff --git a/app/assets/javascripts/issuable/components/status_box.vue b/app/assets/javascripts/issuable/components/status_box.vue
index 498dc859186..d72ee5c6757 100644
--- a/app/assets/javascripts/issuable/components/status_box.vue
+++ b/app/assets/javascripts/issuable/components/status_box.vue
@@ -63,6 +63,8 @@ export default {
},
},
data() {
+ if (!this.iid) return { state: this.initialState };
+
if (this.initialState) {
badgeState.state = this.initialState;
}
@@ -74,8 +76,7 @@ export default {
return [
CLASSES[this.state],
{
- 'gl-vertical-align-bottom':
- this.issuableType === IssuableType.MergeRequest && this.glFeatures.updatedMrHeader,
+ 'gl-vertical-align-bottom': this.issuableType === IssuableType.MergeRequest,
},
];
},
diff --git a/app/assets/javascripts/issuable/issuable_form.js b/app/assets/javascripts/issuable/issuable_form.js
index 8e76a33c7dd..38453072af8 100644
--- a/app/assets/javascripts/issuable/issuable_form.js
+++ b/app/assets/javascripts/issuable/issuable_form.js
@@ -46,6 +46,9 @@ function getFallbackKey() {
export default class IssuableForm {
constructor(form) {
+ if (form.length === 0) {
+ return;
+ }
this.form = form;
this.toggleWip = this.toggleWip.bind(this);
this.renderWipExplanation = this.renderWipExplanation.bind(this);
diff --git a/app/assets/javascripts/issuable/popover/components/issue_popover.vue b/app/assets/javascripts/issuable/popover/components/issue_popover.vue
new file mode 100644
index 00000000000..0cafaa1e500
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/components/issue_popover.vue
@@ -0,0 +1,83 @@
+<script>
+import { GlPopover, GlSkeletonLoader } from '@gitlab/ui';
+import StatusBox from '~/issuable/components/status_box.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import query from '../queries/issue.query.graphql';
+
+export default {
+ components: {
+ GlPopover,
+ GlSkeletonLoader,
+ StatusBox,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ target: {
+ type: HTMLAnchorElement,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ iid: {
+ type: String,
+ required: true,
+ },
+ cachedTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ issue: {},
+ };
+ },
+ computed: {
+ formattedTime() {
+ return this.timeFormatted(this.issue.createdAt);
+ },
+ title() {
+ return this.issue?.title || this.cachedTitle;
+ },
+ showDetails() {
+ return Object.keys(this.issue).length > 0;
+ },
+ },
+ apollo: {
+ issue: {
+ query,
+ update: (data) => data.project.issue,
+ variables() {
+ const { projectPath, iid } = this;
+
+ return {
+ projectPath,
+ iid,
+ };
+ },
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover :target="target" boundary="viewport" placement="top" show>
+ <gl-skeleton-loader v-if="$apollo.queries.issue.loading" :height="15">
+ <rect width="250" height="15" rx="4" />
+ </gl-skeleton-loader>
+ <div v-else-if="showDetails" class="gl-display-flex gl-align-items-center">
+ <status-box issuable-type="issue" :initial-state="issue.state" />
+ <span class="gl-text-secondary">
+ {{ __('Opened') }} <time :datetime="issue.createdAt">{{ formattedTime }}</time>
+ </span>
+ </div>
+ <h5 v-if="!$apollo.queries.issue.loading" class="gl-my-3">{{ title }}</h5>
+ <!-- eslint-disable @gitlab/vue-require-i18n-strings -->
+ <div class="gl-text-secondary">
+ {{ `${projectPath}#${iid}` }}
+ </div>
+ <!-- eslint-enable @gitlab/vue-require-i18n-strings -->
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/issuable/popover/components/mr_popover.vue b/app/assets/javascripts/issuable/popover/components/mr_popover.vue
new file mode 100644
index 00000000000..92994809362
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/components/mr_popover.vue
@@ -0,0 +1,116 @@
+<script>
+/* eslint-disable @gitlab/vue-require-i18n-strings */
+import { GlBadge, GlPopover, GlSkeletonLoader } from '@gitlab/ui';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { mrStates, humanMRStates } from '../constants';
+import query from '../queries/merge_request.query.graphql';
+
+export default {
+ // name: 'MRPopover' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
+ name: 'MRPopover', // eslint-disable-line @gitlab/require-i18n-strings
+ components: {
+ GlBadge,
+ GlPopover,
+ GlSkeletonLoader,
+ CiIcon,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ target: {
+ type: HTMLAnchorElement,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ iid: {
+ type: String,
+ required: true,
+ },
+ cachedTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ mergeRequest: {},
+ };
+ },
+ computed: {
+ detailedStatus() {
+ return this.mergeRequest.headPipeline && this.mergeRequest.headPipeline.detailedStatus;
+ },
+ formattedTime() {
+ return this.timeFormatted(this.mergeRequest.createdAt);
+ },
+ badgeVariant() {
+ switch (this.mergeRequest.state) {
+ case mrStates.merged:
+ return 'info';
+ case mrStates.closed:
+ return 'danger';
+ default:
+ return 'success';
+ }
+ },
+ stateHumanName() {
+ switch (this.mergeRequest.state) {
+ case mrStates.merged:
+ return humanMRStates.merged;
+ case mrStates.closed:
+ return humanMRStates.closed;
+ default:
+ return humanMRStates.open;
+ }
+ },
+ title() {
+ return this.mergeRequest?.title || this.cachedTitle;
+ },
+ showDetails() {
+ return Object.keys(this.mergeRequest).length > 0;
+ },
+ },
+ apollo: {
+ mergeRequest: {
+ query,
+ update: (data) => data.project.mergeRequest,
+ variables() {
+ const { projectPath, iid } = this;
+
+ return {
+ projectPath,
+ iid,
+ };
+ },
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover :target="target" boundary="viewport" placement="top" show>
+ <div class="mr-popover">
+ <gl-skeleton-loader v-if="$apollo.queries.mergeRequest.loading" :height="15">
+ <rect width="250" height="15" rx="4" />
+ </gl-skeleton-loader>
+ <div v-else-if="showDetails" class="d-flex align-items-center justify-content-between">
+ <div class="d-inline-flex align-items-center">
+ <gl-badge class="gl-mr-3" :variant="badgeVariant">
+ {{ stateHumanName }}
+ </gl-badge>
+ <span class="gl-text-secondary">Opened <time v-text="formattedTime"></time></span>
+ </div>
+ <ci-icon v-if="detailedStatus" :status="detailedStatus" />
+ </div>
+ <h5 v-if="!$apollo.queries.mergeRequest.loading" class="my-2">{{ title }}</h5>
+ <!-- eslint-disable @gitlab/vue-require-i18n-strings -->
+ <div class="gl-text-secondary">
+ {{ `${projectPath}!${iid}` }}
+ </div>
+ <!-- eslint-enable @gitlab/vue-require-i18n-strings -->
+ </div>
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/issuable/popover/constants.js b/app/assets/javascripts/issuable/popover/constants.js
new file mode 100644
index 00000000000..352bc635293
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/constants.js
@@ -0,0 +1,13 @@
+import { __ } from '~/locale';
+
+export const mrStates = {
+ merged: 'merged',
+ closed: 'closed',
+ open: 'open',
+};
+
+export const humanMRStates = {
+ merged: __('Merged'),
+ closed: __('Closed'),
+ open: __('Open'),
+};
diff --git a/app/assets/javascripts/issuable/popover/index.js b/app/assets/javascripts/issuable/popover/index.js
new file mode 100644
index 00000000000..de3c8160b7a
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/index.js
@@ -0,0 +1,85 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import IssuePopover from './components/issue_popover.vue';
+import MRPopover from './components/mr_popover.vue';
+
+const componentsByReferenceType = {
+ issue: IssuePopover,
+ merge_request: MRPopover,
+};
+
+let renderFn;
+
+const handleIssuablePopoverMouseOut = ({ target }) => {
+ target.removeEventListener('mouseleave', handleIssuablePopoverMouseOut);
+
+ if (renderFn) {
+ clearTimeout(renderFn);
+ }
+};
+
+const popoverMountedAttr = 'data-popover-mounted';
+
+/**
+ * Adds a MergeRequestPopover component to the body, hands over as much data as the target element has in data attributes.
+ * loads based on data-project-path and data-iid more data about an MR from the API and sets it on the popover
+ */
+const handleIssuablePopoverMount = ({
+ apolloProvider,
+ projectPath,
+ title,
+ iid,
+ referenceType,
+ target,
+}) => {
+ // Add listener to actually remove it again
+ target.addEventListener('mouseleave', handleIssuablePopoverMouseOut);
+
+ renderFn = setTimeout(() => {
+ const PopoverComponent = Vue.extend(componentsByReferenceType[referenceType]);
+ new PopoverComponent({
+ propsData: {
+ target,
+ projectPath,
+ iid,
+ cachedTitle: title,
+ },
+ apolloProvider,
+ }).$mount();
+
+ target.setAttribute(popoverMountedAttr, true);
+ }, 200); // 200ms delay so not every mouseover triggers Popover + API Call
+};
+
+export default (elements) => {
+ if (elements.length > 0) {
+ Vue.use(VueApollo);
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+ const listenerAddedAttr = 'data-popover-listener-added';
+
+ elements.forEach((el) => {
+ const { projectPath, iid, referenceType } = el.dataset;
+ const title = el.dataset.mrTitle || el.title;
+
+ if (!el.getAttribute(listenerAddedAttr) && projectPath && title && iid && referenceType) {
+ el.addEventListener('mouseenter', ({ target }) => {
+ if (!el.getAttribute(popoverMountedAttr)) {
+ handleIssuablePopoverMount({
+ apolloProvider,
+ projectPath,
+ title,
+ iid,
+ referenceType,
+ target,
+ });
+ }
+ });
+ el.setAttribute(listenerAddedAttr, true);
+ }
+ });
+ }
+};
diff --git a/app/assets/javascripts/issuable/popover/queries/issue.query.graphql b/app/assets/javascripts/issuable/popover/queries/issue.query.graphql
new file mode 100644
index 00000000000..47a62e2b6ea
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/queries/issue.query.graphql
@@ -0,0 +1,11 @@
+query issue($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ id
+ issue(iid: $iid) {
+ id
+ title
+ createdAt
+ state
+ }
+ }
+}
diff --git a/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql b/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql
new file mode 100644
index 00000000000..7cd344c1d5e
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql
@@ -0,0 +1,19 @@
+query mergeRequest($projectPath: ID!, $iid: String!) {
+ project(fullPath: $projectPath) {
+ id
+ mergeRequest(iid: $iid) {
+ id
+ title
+ createdAt
+ state
+ headPipeline {
+ id
+ detailedStatus {
+ id
+ icon
+ group
+ }
+ }
+ }
+ }
+}