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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-09-14 03:06:25 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-09-14 03:06:25 +0300
commita93dfc1b7e55b118b1cf4a67afeb46556292914c (patch)
tree65b874b7940d0d05c4ebedaef43b8a1009362651 /app
parent188a57f93bba5953800de490fcc6246966a073fd (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js19
-rw-r--r--app/assets/javascripts/notes/stores/actions.js126
-rw-r--r--app/assets/javascripts/releases/components/milestone_list.vue45
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue22
-rw-r--r--app/services/issues/zoom_link_service.rb90
5 files changed, 218 insertions, 84 deletions
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 3d239d8cb6b..4d3dbec435f 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -1,31 +1,28 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
+import axios from '~/lib/utils/axios_utils';
import * as constants from '../constants';
-Vue.use(VueResource);
-
export default {
fetchDiscussions(endpoint, filter, persistFilter = true) {
const config =
filter !== undefined
? { params: { notes_filter: filter, persist_filter: persistFilter } }
: null;
- return Vue.http.get(endpoint, config);
+ return axios.get(endpoint, config);
},
replyToDiscussion(endpoint, data) {
- return Vue.http.post(endpoint, data, { emulateJSON: true });
+ return axios.post(endpoint, data);
},
updateNote(endpoint, data) {
- return Vue.http.put(endpoint, data, { emulateJSON: true });
+ return axios.put(endpoint, data);
},
createNewNote(endpoint, data) {
- return Vue.http.post(endpoint, data, { emulateJSON: true });
+ return axios.post(endpoint, data);
},
toggleResolveNote(endpoint, isResolved) {
const { RESOLVE_NOTE_METHOD_NAME, UNRESOLVE_NOTE_METHOD_NAME } = constants;
const method = isResolved ? UNRESOLVE_NOTE_METHOD_NAME : RESOLVE_NOTE_METHOD_NAME;
- return Vue.http[method](endpoint);
+ return axios[method](endpoint);
},
poll(data = {}) {
const endpoint = data.notesData.notesPath;
@@ -36,9 +33,9 @@ export default {
},
};
- return Vue.http.get(endpoint, options);
+ return axios.get(endpoint, options);
},
toggleIssueState(endpoint, data) {
- return Vue.http.put(endpoint, data);
+ return axios.put(endpoint, data);
},
};
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 411bd585672..6c236981a24 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -47,13 +47,10 @@ export const setNotesFetchedState = ({ commit }, state) =>
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
export const fetchDiscussions = ({ commit, dispatch }, { path, filter, persistFilter }) =>
- service
- .fetchDiscussions(path, filter, persistFilter)
- .then(res => res.json())
- .then(discussions => {
- commit(types.SET_INITIAL_DISCUSSIONS, discussions);
- dispatch('updateResolvableDiscussionsCounts');
- });
+ service.fetchDiscussions(path, filter, persistFilter).then(({ data }) => {
+ commit(types.SET_INITIAL_DISCUSSIONS, data);
+ dispatch('updateResolvableDiscussionsCounts');
+ });
export const updateDiscussion = ({ commit, state }, discussion) => {
commit(types.UPDATE_DISCUSSION, discussion);
@@ -80,13 +77,10 @@ export const deleteNote = ({ dispatch }, note) =>
});
export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
- service
- .updateNote(endpoint, note)
- .then(res => res.json())
- .then(res => {
- commit(types.UPDATE_NOTE, res);
- dispatch('startTaskList');
- });
+ service.updateNote(endpoint, note).then(({ data }) => {
+ commit(types.UPDATE_NOTE, data);
+ dispatch('startTaskList');
+ });
export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => {
const { notesById } = getters;
@@ -110,40 +104,37 @@ export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes)
});
};
-export const replyToDiscussion = ({ commit, state, getters, dispatch }, { endpoint, data }) =>
- service
- .replyToDiscussion(endpoint, data)
- .then(res => res.json())
- .then(res => {
- if (res.discussion) {
- commit(types.UPDATE_DISCUSSION, res.discussion);
+export const replyToDiscussion = (
+ { commit, state, getters, dispatch },
+ { endpoint, data: reply },
+) =>
+ service.replyToDiscussion(endpoint, reply).then(({ data }) => {
+ if (data.discussion) {
+ commit(types.UPDATE_DISCUSSION, data.discussion);
- updateOrCreateNotes({ commit, state, getters, dispatch }, res.discussion.notes);
+ updateOrCreateNotes({ commit, state, getters, dispatch }, data.discussion.notes);
- dispatch('updateMergeRequestWidget');
- dispatch('startTaskList');
- dispatch('updateResolvableDiscussionsCounts');
- } else {
- commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
- }
+ dispatch('updateMergeRequestWidget');
+ dispatch('startTaskList');
+ dispatch('updateResolvableDiscussionsCounts');
+ } else {
+ commit(types.ADD_NEW_REPLY_TO_DISCUSSION, data);
+ }
- return res;
- });
+ return data;
+ });
-export const createNewNote = ({ commit, dispatch }, { endpoint, data }) =>
- service
- .createNewNote(endpoint, data)
- .then(res => res.json())
- .then(res => {
- if (!res.errors) {
- commit(types.ADD_NEW_NOTE, res);
-
- dispatch('updateMergeRequestWidget');
- dispatch('startTaskList');
- dispatch('updateResolvableDiscussionsCounts');
- }
- return res;
- });
+export const createNewNote = ({ commit, dispatch }, { endpoint, data: reply }) =>
+ service.createNewNote(endpoint, reply).then(({ data }) => {
+ if (!data.errors) {
+ commit(types.ADD_NEW_NOTE, data);
+
+ dispatch('updateMergeRequestWidget');
+ dispatch('startTaskList');
+ dispatch('updateResolvableDiscussionsCounts');
+ }
+ return data;
+ });
export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES);
@@ -165,41 +156,32 @@ export const resolveDiscussion = ({ state, dispatch, getters }, { discussionId }
};
export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved, discussion }) =>
- service
- .toggleResolveNote(endpoint, isResolved)
- .then(res => res.json())
- .then(res => {
- const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
+ service.toggleResolveNote(endpoint, isResolved).then(({ data }) => {
+ const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE;
- commit(mutationType, res);
+ commit(mutationType, data);
- dispatch('updateResolvableDiscussionsCounts');
+ dispatch('updateResolvableDiscussionsCounts');
- dispatch('updateMergeRequestWidget');
- });
+ dispatch('updateMergeRequestWidget');
+ });
export const closeIssue = ({ commit, dispatch, state }) => {
dispatch('toggleStateButtonLoading', true);
- return service
- .toggleIssueState(state.notesData.closePath)
- .then(res => res.json())
- .then(data => {
- commit(types.CLOSE_ISSUE);
- dispatch('emitStateChangedEvent', data);
- dispatch('toggleStateButtonLoading', false);
- });
+ return service.toggleIssueState(state.notesData.closePath).then(({ data }) => {
+ commit(types.CLOSE_ISSUE);
+ dispatch('emitStateChangedEvent', data);
+ dispatch('toggleStateButtonLoading', false);
+ });
};
export const reopenIssue = ({ commit, dispatch, state }) => {
dispatch('toggleStateButtonLoading', true);
- return service
- .toggleIssueState(state.notesData.reopenPath)
- .then(res => res.json())
- .then(data => {
- commit(types.REOPEN_ISSUE);
- dispatch('emitStateChangedEvent', data);
- dispatch('toggleStateButtonLoading', false);
- });
+ return service.toggleIssueState(state.notesData.reopenPath).then(({ data }) => {
+ commit(types.REOPEN_ISSUE);
+ dispatch('emitStateChangedEvent', data);
+ dispatch('toggleStateButtonLoading', false);
+ });
};
export const toggleStateButtonLoading = ({ commit }, value) =>
@@ -340,8 +322,7 @@ export const poll = ({ commit, state, getters, dispatch }) => {
resource: service,
method: 'poll',
data: state,
- successCallback: resp =>
- resp.json().then(data => pollSuccessCallBack(data, commit, state, getters, dispatch)),
+ successCallback: ({ data }) => pollSuccessCallBack(data, commit, state, getters, dispatch),
errorCallback: () => Flash(__('Something went wrong while fetching latest comments.')),
});
@@ -376,8 +357,7 @@ export const fetchData = ({ commit, state, getters }) => {
service
.poll(requestData)
- .then(resp => resp.json)
- .then(data => pollSuccessCallBack(data, commit, state, getters))
+ .then(({ data }) => pollSuccessCallBack(data, commit, state, getters))
.catch(() => Flash(__('Something went wrong while fetching latest comments.')));
};
diff --git a/app/assets/javascripts/releases/components/milestone_list.vue b/app/assets/javascripts/releases/components/milestone_list.vue
new file mode 100644
index 00000000000..53416f0ab4d
--- /dev/null
+++ b/app/assets/javascripts/releases/components/milestone_list.vue
@@ -0,0 +1,45 @@
+<script>
+import { GlLink, GlTooltipDirective } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { s__ } from '~/locale';
+
+export default {
+ name: 'MilestoneList',
+ components: {
+ GlLink,
+ Icon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ milestones: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ labelText() {
+ return this.milestones.length === 1 ? s__('Milestone') : s__('Milestones');
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <icon name="flag" class="align-middle" /> <span class="js-label-text">{{ labelText }}</span>
+ <template v-for="(milestone, index) in milestones">
+ <gl-link
+ :key="milestone.id"
+ v-gl-tooltip
+ :title="milestone.description"
+ :href="milestone.web_url"
+ >
+ {{ milestone.title }}
+ </gl-link>
+ <template v-if="index !== milestones.length - 1">
+ &bull;
+ </template>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 88b6b4732b1..2dacd8549ad 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -5,6 +5,7 @@ import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
+import MilestoneList from './milestone_list.vue';
import { __, sprintf } from '../../locale';
export default {
@@ -14,6 +15,7 @@ export default {
GlBadge,
Icon,
UserAvatarLink,
+ MilestoneList,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -49,6 +51,20 @@ export default {
hasAuthor() {
return !_.isEmpty(this.author);
},
+ milestones() {
+ // At the moment, a release can only be associated to
+ // one milestone. This will be expanded to be many-to-many
+ // in the near future, so we pass the milestone as an
+ // array here in anticipation of this change.
+ return [this.release.milestone];
+ },
+ shouldRenderMilestones() {
+ // Similar to the `milestones` computed above,
+ // this check will need to be updated once
+ // the API begins sending an array of milestones
+ // instead of just a single object.
+ return Boolean(this.release.milestone);
+ },
},
};
</script>
@@ -73,6 +89,12 @@ export default {
<span v-gl-tooltip.bottom :title="__('Tag')">{{ release.tag_name }}</span>
</div>
+ <milestone-list
+ v-if="shouldRenderMilestones"
+ class="append-right-4 js-milestone-list"
+ :milestones="milestones"
+ />
+
<div class="append-right-4">
&bull;
<span v-gl-tooltip.bottom :title="tooltipTitle(release.released_at)">
diff --git a/app/services/issues/zoom_link_service.rb b/app/services/issues/zoom_link_service.rb
new file mode 100644
index 00000000000..a061ab22875
--- /dev/null
+++ b/app/services/issues/zoom_link_service.rb
@@ -0,0 +1,90 @@
+# frozen_string_literal: true
+
+module Issues
+ class ZoomLinkService < Issues::BaseService
+ def initialize(issue, user)
+ super(issue.project, user)
+
+ @issue = issue
+ end
+
+ def add_link(link)
+ if can_add_link? && (link = parse_link(link))
+ success(_('Zoom meeting added'), append_to_description(link))
+ else
+ error(_('Failed to add a Zoom meeting'))
+ end
+ end
+
+ def can_add_link?
+ available? && !link_in_issue_description?
+ end
+
+ def remove_link
+ if can_remove_link?
+ success(_('Zoom meeting removed'), remove_from_description)
+ else
+ error(_('Failed to remove a Zoom meeting'))
+ end
+ end
+
+ def can_remove_link?
+ available? && link_in_issue_description?
+ end
+
+ def parse_link(link)
+ Gitlab::ZoomLinkExtractor.new(link).links.last
+ end
+
+ private
+
+ attr_reader :issue
+
+ def issue_description
+ issue.description || ''
+ end
+
+ def success(message, description)
+ ServiceResponse
+ .success(message: message, payload: { description: description })
+ end
+
+ def error(message)
+ ServiceResponse.error(message: message)
+ end
+
+ def append_to_description(link)
+ "#{issue_description}\n\n#{link}"
+ end
+
+ def remove_from_description
+ link = parse_link(issue_description)
+ return issue_description unless link
+
+ issue_description.delete_suffix(link).rstrip
+ end
+
+ def link_in_issue_description?
+ link = extract_link_from_issue_description
+ return unless link
+
+ Gitlab::ZoomLinkExtractor.new(link).match?
+ end
+
+ def extract_link_from_issue_description
+ issue_description[/(\S+)\z/, 1]
+ end
+
+ def available?
+ feature_enabled? && can?
+ end
+
+ def feature_enabled?
+ Feature.enabled?(:issue_zoom_integration, project)
+ end
+
+ def can?
+ current_user.can?(:update_issue, project)
+ end
+ end
+end