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-05-24 12:09:17 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-24 12:09:17 +0300
commit43c14d2d9245aea5964d52d3e4915be1126977cb (patch)
tree769e314e13e40059f6b5ff228e670e278d201e06 /app/assets/javascripts/issuable
parentecf2b5b6048d8f289d085b5d7951381c1ef4dca0 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/issuable')
-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.js69
-rw-r--r--app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql19
4 files changed, 217 insertions, 0 deletions
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..f467d7e93b1
--- /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, GlDeprecatedSkeletonLoading as GlSkeletonLoading } 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,
+ GlSkeletonLoading,
+ CiIcon,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ target: {
+ type: HTMLAnchorElement,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ mergeRequestIID: {
+ type: String,
+ required: true,
+ },
+ mergeRequestTitle: {
+ 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.mergeRequestTitle;
+ },
+ showDetails() {
+ return Object.keys(this.mergeRequest).length > 0;
+ },
+ },
+ apollo: {
+ mergeRequest: {
+ query,
+ update: (data) => data.project.mergeRequest,
+ variables() {
+ const { projectPath, mergeRequestIID } = this;
+
+ return {
+ projectPath,
+ mergeRequestIID,
+ };
+ },
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover :target="target" boundary="viewport" placement="top" show>
+ <div class="mr-popover">
+ <div v-if="$apollo.queries.mergeRequest.loading">
+ <gl-skeleton-loading :lines="1" class="animation-container-small mt-1" />
+ </div>
+ <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}!${mergeRequestIID}` }}
+ </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..1f0a00bc286
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/index.js
@@ -0,0 +1,69 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import MRPopover from './components/mr_popover.vue';
+
+let renderedPopover;
+let renderFn;
+
+const handleIssuablePopoverMouseOut = ({ target }) => {
+ target.removeEventListener('mouseleave', handleIssuablePopoverMouseOut);
+
+ if (renderFn) {
+ clearTimeout(renderFn);
+ }
+ if (renderedPopover) {
+ renderedPopover.$destroy();
+ renderedPopover = null;
+ }
+};
+
+/**
+ * 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 }) => ({
+ target,
+}) => {
+ // Add listener to actually remove it again
+ target.addEventListener('mouseleave', handleIssuablePopoverMouseOut);
+
+ renderFn = setTimeout(() => {
+ const MRPopoverComponent = Vue.extend(MRPopover);
+ renderedPopover = new MRPopoverComponent({
+ propsData: {
+ target,
+ projectPath,
+ mergeRequestIID: iid,
+ mergeRequestTitle: title,
+ },
+ apolloProvider,
+ });
+
+ renderedPopover.$mount();
+ }, 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 } = el.dataset;
+ const title = el.dataset.mrTitle || el.title;
+
+ if (!el.getAttribute(listenerAddedAttr) && projectPath && title && iid) {
+ el.addEventListener(
+ 'mouseenter',
+ handleIssuablePopoverMount({ apolloProvider, projectPath, title, iid }),
+ );
+ el.setAttribute(listenerAddedAttr, true);
+ }
+ });
+ }
+};
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..b3e5d89d495
--- /dev/null
+++ b/app/assets/javascripts/issuable/popover/queries/merge_request.query.graphql
@@ -0,0 +1,19 @@
+query mergeRequest($projectPath: ID!, $mergeRequestIID: String!) {
+ project(fullPath: $projectPath) {
+ id
+ mergeRequest(iid: $mergeRequestIID) {
+ id
+ title
+ createdAt
+ state
+ headPipeline {
+ id
+ detailedStatus {
+ id
+ icon
+ group
+ }
+ }
+ }
+ }
+}