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>2021-12-01 21:15:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-01 21:15:19 +0300
commit5fc2d78fb96b0fd50dfb737190fd411033b3c3ab (patch)
tree24cb469e61661c923a1398505b2bb928612f80d4 /app/assets/javascripts/milestones
parent66629d156e2420269ed53eff3dca0912cfe848e2 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/milestones')
-rw-r--r--app/assets/javascripts/milestones/components/delete_milestone_modal.vue137
-rw-r--r--app/assets/javascripts/milestones/components/promote_milestone_modal.vue104
-rw-r--r--app/assets/javascripts/milestones/delete_milestone_modal_init.js75
-rw-r--r--app/assets/javascripts/milestones/event_hub.js3
-rw-r--r--app/assets/javascripts/milestones/form.js22
-rw-r--r--app/assets/javascripts/milestones/init_milestones_show.js11
-rw-r--r--app/assets/javascripts/milestones/milestone.js49
-rw-r--r--app/assets/javascripts/milestones/milestone_select.js273
-rw-r--r--app/assets/javascripts/milestones/promote_milestone_modal_init.js19
9 files changed, 693 insertions, 0 deletions
diff --git a/app/assets/javascripts/milestones/components/delete_milestone_modal.vue b/app/assets/javascripts/milestones/components/delete_milestone_modal.vue
new file mode 100644
index 00000000000..34f9fe778ea
--- /dev/null
+++ b/app/assets/javascripts/milestones/components/delete_milestone_modal.vue
@@ -0,0 +1,137 @@
+<script>
+import { GlSafeHtmlDirective as SafeHtml, GlModal } from '@gitlab/ui';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+
+import { redirectTo } from '~/lib/utils/url_utility';
+import { __, n__, s__, sprintf } from '~/locale';
+import eventHub from '../event_hub';
+
+export default {
+ components: {
+ GlModal,
+ },
+ directives: {
+ SafeHtml,
+ },
+ props: {
+ issueCount: {
+ type: Number,
+ required: true,
+ },
+ mergeRequestCount: {
+ type: Number,
+ required: true,
+ },
+ milestoneId: {
+ type: Number,
+ required: true,
+ },
+ milestoneTitle: {
+ type: String,
+ required: true,
+ },
+ milestoneUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ text() {
+ const milestoneTitle = sprintf('<strong>%{milestoneTitle}</strong>', {
+ milestoneTitle: this.milestoneTitle,
+ });
+
+ if (this.issueCount === 0 && this.mergeRequestCount === 0) {
+ return sprintf(
+ s__(`Milestones|
+You’re about to permanently delete the milestone %{milestoneTitle}.
+This milestone is not currently used in any issues or merge requests.`),
+ {
+ milestoneTitle,
+ },
+ false,
+ );
+ }
+
+ return sprintf(
+ s__(`Milestones|
+You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}.
+Once deleted, it cannot be undone or recovered.`),
+ {
+ milestoneTitle,
+ issuesWithCount: n__('%d issue', '%d issues', this.issueCount),
+ mergeRequestsWithCount: n__(
+ '%d merge request',
+ '%d merge requests',
+ this.mergeRequestCount,
+ ),
+ },
+ false,
+ );
+ },
+ title() {
+ return sprintf(s__('Milestones|Delete milestone %{milestoneTitle}?'), {
+ milestoneTitle: this.milestoneTitle,
+ });
+ },
+ },
+ methods: {
+ onSubmit() {
+ eventHub.$emit('deleteMilestoneModal.requestStarted', this.milestoneUrl);
+
+ return axios
+ .delete(this.milestoneUrl)
+ .then((response) => {
+ eventHub.$emit('deleteMilestoneModal.requestFinished', {
+ milestoneUrl: this.milestoneUrl,
+ successful: true,
+ });
+
+ // follow the rediect to milestones overview page
+ redirectTo(response.request.responseURL);
+ })
+ .catch((error) => {
+ eventHub.$emit('deleteMilestoneModal.requestFinished', {
+ milestoneUrl: this.milestoneUrl,
+ successful: false,
+ });
+
+ if (error.response && error.response.status === 404) {
+ createFlash({
+ message: sprintf(s__('Milestones|Milestone %{milestoneTitle} was not found'), {
+ milestoneTitle: this.milestoneTitle,
+ }),
+ });
+ } else {
+ createFlash({
+ message: sprintf(s__('Milestones|Failed to delete milestone %{milestoneTitle}'), {
+ milestoneTitle: this.milestoneTitle,
+ }),
+ });
+ }
+ throw error;
+ });
+ },
+ },
+ primaryProps: {
+ text: s__('Milestones|Delete milestone'),
+ attributes: [{ variant: 'danger' }, { category: 'primary' }],
+ },
+ cancelProps: {
+ text: __('Cancel'),
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ modal-id="delete-milestone-modal"
+ :title="title"
+ :action-primary="$options.primaryProps"
+ :action-cancel="$options.cancelProps"
+ @primary="onSubmit"
+ >
+ <p v-safe-html="text"></p>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/milestones/components/promote_milestone_modal.vue b/app/assets/javascripts/milestones/components/promote_milestone_modal.vue
new file mode 100644
index 00000000000..b41611001ab
--- /dev/null
+++ b/app/assets/javascripts/milestones/components/promote_milestone_modal.vue
@@ -0,0 +1,104 @@
+<script>
+import { GlModal } from '@gitlab/ui';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { __, s__, sprintf } from '~/locale';
+
+export default {
+ components: {
+ GlModal,
+ },
+ data() {
+ return {
+ milestoneTitle: '',
+ url: '',
+ groupName: '',
+ currentButton: null,
+ visible: false,
+ };
+ },
+ computed: {
+ title() {
+ return sprintf(s__('Milestones|Promote %{milestoneTitle} to group milestone?'), {
+ milestoneTitle: this.milestoneTitle,
+ });
+ },
+ text() {
+ return sprintf(
+ s__(`Milestones|Promoting %{milestoneTitle} will make it available for all projects inside %{groupName}.
+ Existing project milestones with the same title will be merged.`),
+ { milestoneTitle: this.milestoneTitle, groupName: this.groupName },
+ );
+ },
+ },
+ mounted() {
+ this.getButtons().forEach((button) => {
+ button.addEventListener('click', this.onPromoteButtonClick);
+ button.removeAttribute('disabled');
+ });
+ },
+ beforeDestroy() {
+ this.getButtons().forEach((button) => {
+ button.removeEventListener('click', this.onPromoteButtonClick);
+ });
+ },
+ methods: {
+ onPromoteButtonClick({ currentTarget }) {
+ const { milestoneTitle, url, groupName } = currentTarget.dataset;
+ currentTarget.setAttribute('disabled', '');
+ this.visible = true;
+ this.milestoneTitle = milestoneTitle;
+ this.url = url;
+ this.groupName = groupName;
+ this.currentButton = currentTarget;
+ },
+ getButtons() {
+ return document.querySelectorAll('.js-promote-project-milestone-button');
+ },
+ onSubmit() {
+ return axios
+ .post(this.url, { params: { format: 'json' } })
+ .then((response) => {
+ visitUrl(response.data.url);
+ })
+ .catch((error) => {
+ createFlash({
+ message: error,
+ });
+ })
+ .finally(() => {
+ this.visible = false;
+ });
+ },
+ onClose() {
+ this.visible = false;
+ if (this.currentButton) {
+ this.currentButton.removeAttribute('disabled');
+ }
+ },
+ },
+ primaryAction: {
+ text: s__('Milestones|Promote Milestone'),
+ attributes: [{ variant: 'warning' }],
+ },
+ cancelAction: {
+ text: __('Cancel'),
+ attributes: [],
+ },
+};
+</script>
+<template>
+ <gl-modal
+ :visible="visible"
+ modal-id="promote-milestone-modal"
+ :action-primary="$options.primaryAction"
+ :action-cancel="$options.cancelAction"
+ :title="title"
+ @primary="onSubmit"
+ @hide="onClose"
+ >
+ <p>{{ text }}</p>
+ <p>{{ s__('Milestones|This action cannot be reversed.') }}</p>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/milestones/delete_milestone_modal_init.js b/app/assets/javascripts/milestones/delete_milestone_modal_init.js
new file mode 100644
index 00000000000..3aeff2db2e0
--- /dev/null
+++ b/app/assets/javascripts/milestones/delete_milestone_modal_init.js
@@ -0,0 +1,75 @@
+import Vue from 'vue';
+import { BV_SHOW_MODAL } from '~/lib/utils/constants';
+import Translate from '~/vue_shared/translate';
+import DeleteMilestoneModal from './components/delete_milestone_modal.vue';
+import eventHub from './event_hub';
+
+export default () => {
+ Vue.use(Translate);
+
+ const onRequestFinished = ({ milestoneUrl, successful }) => {
+ const button = document.querySelector(
+ `.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`,
+ );
+
+ if (!successful) {
+ button.removeAttribute('disabled');
+ }
+
+ button.querySelector('.js-loading-icon').classList.add('hidden');
+ };
+
+ const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
+
+ const onRequestStarted = (milestoneUrl) => {
+ const button = document.querySelector(
+ `.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`,
+ );
+ button.setAttribute('disabled', '');
+ button.querySelector('.js-loading-icon').classList.remove('hidden');
+ eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
+ };
+
+ return new Vue({
+ el: '#js-delete-milestone-modal',
+ data() {
+ return {
+ modalProps: {
+ milestoneId: -1,
+ milestoneTitle: '',
+ milestoneUrl: '',
+ issueCount: -1,
+ mergeRequestCount: -1,
+ },
+ };
+ },
+ mounted() {
+ eventHub.$on('deleteMilestoneModal.props', this.setModalProps);
+ deleteMilestoneButtons.forEach((button) => {
+ button.removeAttribute('disabled');
+ button.addEventListener('click', () => {
+ this.$root.$emit(BV_SHOW_MODAL, 'delete-milestone-modal');
+ eventHub.$once('deleteMilestoneModal.requestStarted', onRequestStarted);
+
+ this.setModalProps({
+ milestoneId: parseInt(button.dataset.milestoneId, 10),
+ milestoneTitle: button.dataset.milestoneTitle,
+ milestoneUrl: button.dataset.milestoneUrl,
+ issueCount: parseInt(button.dataset.milestoneIssueCount, 10),
+ mergeRequestCount: parseInt(button.dataset.milestoneMergeRequestCount, 10),
+ });
+ });
+ });
+ },
+ methods: {
+ setModalProps(modalProps) {
+ this.modalProps = modalProps;
+ },
+ },
+ render(createElement) {
+ return createElement(DeleteMilestoneModal, {
+ props: this.modalProps,
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/milestones/event_hub.js b/app/assets/javascripts/milestones/event_hub.js
new file mode 100644
index 00000000000..e31806ad199
--- /dev/null
+++ b/app/assets/javascripts/milestones/event_hub.js
@@ -0,0 +1,3 @@
+import createEventHub from '~/helpers/event_hub_factory';
+
+export default createEventHub();
diff --git a/app/assets/javascripts/milestones/form.js b/app/assets/javascripts/milestones/form.js
new file mode 100644
index 00000000000..40d45d7deb8
--- /dev/null
+++ b/app/assets/javascripts/milestones/form.js
@@ -0,0 +1,22 @@
+import $ from 'jquery';
+import initDatePicker from '~/behaviors/date_picker';
+import GLForm from '~/gl_form';
+import ZenMode from '~/zen_mode';
+
+export default (initGFM = true) => {
+ new ZenMode(); // eslint-disable-line no-new
+ initDatePicker();
+
+ // eslint-disable-next-line no-new
+ new GLForm($('.milestone-form'), {
+ emojis: true,
+ members: initGFM,
+ issues: initGFM,
+ mergeRequests: initGFM,
+ epics: initGFM,
+ milestones: initGFM,
+ labels: initGFM,
+ snippets: initGFM,
+ vulnerabilities: initGFM,
+ });
+};
diff --git a/app/assets/javascripts/milestones/init_milestones_show.js b/app/assets/javascripts/milestones/init_milestones_show.js
new file mode 100644
index 00000000000..8939e1535c1
--- /dev/null
+++ b/app/assets/javascripts/milestones/init_milestones_show.js
@@ -0,0 +1,11 @@
+/* eslint-disable no-new */
+
+import Milestone from '~/milestones/milestone';
+import Sidebar from '~/right_sidebar';
+import MountMilestoneSidebar from '~/sidebar/mount_milestone_sidebar';
+
+export default () => {
+ new Milestone();
+ new Sidebar();
+ new MountMilestoneSidebar();
+};
diff --git a/app/assets/javascripts/milestones/milestone.js b/app/assets/javascripts/milestones/milestone.js
new file mode 100644
index 00000000000..2c43bed412e
--- /dev/null
+++ b/app/assets/javascripts/milestones/milestone.js
@@ -0,0 +1,49 @@
+import $ from 'jquery';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+
+export default class Milestone {
+ constructor() {
+ this.bindTabsSwitching();
+ this.loadInitialTab();
+ }
+
+ bindTabsSwitching() {
+ return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
+ const $target = $(e.target);
+
+ window.location.hash = $target.attr('href');
+ this.loadTab($target);
+ });
+ }
+
+ loadInitialTab() {
+ const $target = $(`.js-milestone-tabs a:not(.active)[href="${window.location.hash}"]`);
+
+ if ($target.length) {
+ $target.tab('show');
+ } else {
+ this.loadTab($('.js-milestone-tabs a.active'));
+ }
+ }
+ // eslint-disable-next-line class-methods-use-this
+ loadTab($target) {
+ const endpoint = $target.data('endpoint');
+ const tabElId = $target.attr('href');
+
+ if (endpoint && !$target.hasClass('is-loaded')) {
+ axios
+ .get(endpoint)
+ .then(({ data }) => {
+ $(tabElId).html(data.html);
+ $target.addClass('is-loaded');
+ })
+ .catch(() =>
+ createFlash({
+ message: __('Error loading milestone tab'),
+ }),
+ );
+ }
+ }
+}
diff --git a/app/assets/javascripts/milestones/milestone_select.js b/app/assets/javascripts/milestones/milestone_select.js
new file mode 100644
index 00000000000..91780d5ee01
--- /dev/null
+++ b/app/assets/javascripts/milestones/milestone_select.js
@@ -0,0 +1,273 @@
+/* eslint-disable one-var, no-self-compare, consistent-return, no-param-reassign, no-shadow */
+/* global Issuable */
+
+import $ from 'jquery';
+import { template, escape } from 'lodash';
+import Api from '~/api';
+import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
+import { __, sprintf } from '~/locale';
+import { sortMilestonesByDueDate } from '~/milestones/milestone_utils';
+import axios from '~/lib/utils/axios_utils';
+import { timeFor, parsePikadayDate, dateInWords } from '~/lib/utils/datetime_utility';
+
+export default class MilestoneSelect {
+ constructor(currentProject, els, options = {}) {
+ if (currentProject !== null) {
+ this.currentProject =
+ typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
+ }
+
+ MilestoneSelect.init(els, options);
+ }
+
+ static init(els, options) {
+ let $els = $(els);
+
+ if (!els) {
+ $els = $('.js-milestone-select');
+ }
+
+ $els.each((i, dropdown) => {
+ let milestoneLinkNoneTemplate,
+ milestoneLinkTemplate,
+ milestoneExpiredLinkTemplate,
+ selectedMilestone,
+ selectedMilestoneDefault;
+ const $dropdown = $(dropdown);
+ const issueUpdateURL = $dropdown.data('issueUpdate');
+ const showNo = $dropdown.data('showNo');
+ const showAny = $dropdown.data('showAny');
+ const showMenuAbove = $dropdown.data('showMenuAbove');
+ const showUpcoming = $dropdown.data('showUpcoming');
+ const showStarted = $dropdown.data('showStarted');
+ const useId = $dropdown.data('useId');
+ const defaultLabel = $dropdown.data('defaultLabel');
+ const defaultNo = $dropdown.data('defaultNo');
+ const abilityName = $dropdown.data('abilityName');
+ const $selectBox = $dropdown.closest('.selectbox');
+ const $block = $selectBox.closest('.block');
+ const $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon');
+ const $value = $block.find('.value');
+ const $loading = $block.find('.block-loading').addClass('gl-display-none');
+ selectedMilestoneDefault = showAny ? '' : null;
+ selectedMilestoneDefault =
+ showNo && defaultNo ? __('No milestone') : selectedMilestoneDefault;
+ selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault;
+
+ if (issueUpdateURL) {
+ milestoneLinkTemplate = template(
+ '<a href="<%- web_url %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>',
+ );
+ milestoneExpiredLinkTemplate = template(
+ '<a href="<%- web_url %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %> (Past due)</a>',
+ );
+ milestoneLinkNoneTemplate = `<span class="no-value">${__('None')}</span>`;
+ }
+ return initDeprecatedJQueryDropdown($dropdown, {
+ showMenuAbove,
+ data: (term, callback) => {
+ let contextId = parseInt($dropdown.get(0).dataset.projectId, 10);
+ let getMilestones = Api.projectMilestones.bind(Api);
+ const reqParams = { state: 'active', include_parent_milestones: true };
+
+ if (term) {
+ reqParams.search = term.trim();
+ }
+
+ if (!contextId) {
+ contextId = $dropdown.get(0).dataset.groupId;
+ delete reqParams.include_parent_milestones;
+ getMilestones = Api.groupMilestones.bind(Api);
+ }
+
+ // We don't use $.data() as it caches initial value and never updates!
+ return getMilestones(contextId, reqParams)
+ .then(({ data }) =>
+ data
+ .map((m) => ({
+ ...m,
+ // Public API includes `title` instead of `name`.
+ name: m.title,
+ }))
+ .sort(sortMilestonesByDueDate),
+ )
+ .then((data) => {
+ const extraOptions = [];
+ if (showAny) {
+ extraOptions.push({
+ id: null,
+ name: null,
+ title: __('Any milestone'),
+ });
+ }
+ if (showNo && term.trim() === '') {
+ extraOptions.push({
+ id: -1,
+ name: __('No milestone'),
+ title: __('No milestone'),
+ });
+ }
+ if (showUpcoming) {
+ extraOptions.push({
+ id: -2,
+ name: '#upcoming',
+ title: __('Upcoming'),
+ });
+ }
+ if (showStarted) {
+ extraOptions.push({
+ id: -3,
+ name: '#started',
+ title: __('Started'),
+ });
+ }
+ if (extraOptions.length) {
+ extraOptions.push({ type: 'divider' });
+ }
+
+ callback(extraOptions.concat(data));
+ if (showMenuAbove) {
+ $dropdown.data('deprecatedJQueryDropdown').positionMenuAbove();
+ }
+ $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active');
+ });
+ },
+ renderRow: (milestone) => {
+ const milestoneName = milestone.title || milestone.name;
+ let milestoneDisplayName = escape(milestoneName);
+
+ if (milestone.expired) {
+ milestoneDisplayName = sprintf(__('%{milestone} (expired)'), {
+ milestone: milestoneDisplayName,
+ });
+ }
+
+ return `
+ <li data-milestone-id="${escape(milestoneName)}">
+ <a href='#' class='dropdown-menu-milestone-link'>
+ ${milestoneDisplayName}
+ </a>
+ </li>
+ `;
+ },
+ filterable: true,
+ filterRemote: true,
+ search: {
+ fields: ['title'],
+ },
+ selectable: true,
+ toggleLabel: (selected, el) => {
+ if (selected && 'id' in selected && $(el).hasClass('is-active')) {
+ return selected.title;
+ }
+ return defaultLabel;
+ },
+ defaultLabel,
+ fieldName: $dropdown.data('fieldName'),
+ text: (milestone) => escape(milestone.title),
+ id: (milestone) => {
+ if (milestone !== undefined) {
+ if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
+ return milestone.name;
+ }
+
+ return milestone.id;
+ }
+ },
+ hidden: () => {
+ $selectBox.hide();
+ // display:block overrides the hide-collapse rule
+ return $value.css('display', '');
+ },
+ opened: (e) => {
+ const $el = $(e.currentTarget);
+ if (options.handleClick) {
+ selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault;
+ }
+ $('a.is-active', $el).removeClass('is-active');
+ $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active');
+ },
+ vue: false,
+ clicked: (clickEvent) => {
+ const { e } = clickEvent;
+ let selected = clickEvent.selectedObj;
+
+ if (!selected) return;
+
+ if (options.handleClick) {
+ e.preventDefault();
+ options.handleClick(selected);
+ return;
+ }
+
+ const page = $('body').attr('data-page');
+ const isIssueIndex = page === 'projects:issues:index';
+ const isMRIndex = page === page && page === 'projects:merge_requests:index';
+ const isSelecting = selected.name !== selectedMilestone;
+ selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault;
+
+ if (
+ $dropdown.hasClass('js-filter-bulk-update') ||
+ $dropdown.hasClass('js-issuable-form-dropdown')
+ ) {
+ e.preventDefault();
+ return;
+ }
+
+ if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ return Issuable.filterResults($dropdown.closest('form'));
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ return $dropdown.closest('form').submit();
+ }
+
+ selected = $selectBox.find('input[type="hidden"]').val();
+
+ const data = {};
+ data[abilityName] = {};
+ data[abilityName].milestone_id = selected != null ? selected : null;
+ $loading.removeClass('gl-display-none');
+ $dropdown.trigger('loading.gl.dropdown');
+ return axios
+ .put(issueUpdateURL, data)
+ .then(({ data }) => {
+ $dropdown.trigger('loaded.gl.dropdown');
+ $loading.addClass('gl-display-none');
+ $selectBox.hide();
+ $value.css('display', '');
+ if (data.milestone != null) {
+ data.milestone.remaining = timeFor(data.milestone.due_date);
+ data.milestone.name = data.milestone.title;
+ $value.html(
+ data.milestone.expired
+ ? milestoneExpiredLinkTemplate({
+ ...data.milestone,
+ remaining: sprintf(__('%{due_date} (Past due)'), {
+ due_date: dateInWords(parsePikadayDate(data.milestone.due_date)),
+ }),
+ })
+ : milestoneLinkTemplate(data.milestone),
+ );
+ return $sidebarCollapsedValue
+ .attr(
+ 'data-original-title',
+ `${data.milestone.name}<br />${data.milestone.remaining}`,
+ )
+ .find('span')
+ .text(data.milestone.title);
+ }
+ $value.html(milestoneLinkNoneTemplate);
+ return $sidebarCollapsedValue
+ .attr('data-original-title', __('Milestone'))
+ .find('span')
+ .text(__('None'));
+ })
+ .catch(() => {
+ $loading.addClass('gl-display-none');
+ });
+ },
+ });
+ });
+ }
+}
+
+window.MilestoneSelect = MilestoneSelect;
diff --git a/app/assets/javascripts/milestones/promote_milestone_modal_init.js b/app/assets/javascripts/milestones/promote_milestone_modal_init.js
new file mode 100644
index 00000000000..5472b8c684f
--- /dev/null
+++ b/app/assets/javascripts/milestones/promote_milestone_modal_init.js
@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import PromoteMilestoneModal from './components/promote_milestone_modal.vue';
+
+Vue.use(Translate);
+
+export default () => {
+ const promoteMilestoneModal = document.getElementById('promote-milestone-modal');
+ if (!promoteMilestoneModal) {
+ return null;
+ }
+
+ return new Vue({
+ el: promoteMilestoneModal,
+ render(createElement) {
+ return createElement(PromoteMilestoneModal);
+ },
+ });
+};