Skip to content
Snippets Groups Projects
Commit a93dfc1b authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 188a57f9
No related branches found
No related tags found
No related merge requests found
Showing
with 607 additions and 390 deletions
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;
Loading
Loading
@@ -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);
},
};
Loading
Loading
@@ -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);
Loading
Loading
@@ -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;
Loading
Loading
@@ -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);
 
Loading
Loading
@@ -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) =>
Loading
Loading
@@ -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.')),
});
 
Loading
Loading
@@ -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.')));
};
 
Loading
Loading
<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>
Loading
Loading
@@ -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 {
Loading
Loading
@@ -14,6 +15,7 @@ export default {
GlBadge,
Icon,
UserAvatarLink,
MilestoneList,
},
directives: {
GlTooltip: GlTooltipDirective,
Loading
Loading
@@ -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>
Loading
Loading
@@ -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)">
Loading
Loading
# 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
---
title: Remove vue-resource from notes service
merge_request: 32934
author: Lee Tickett
type: other
Loading
Loading
@@ -64,6 +64,8 @@ The following quick actions are applicable to descriptions, discussions and thre
| `/create_merge_request <branch name>` | ✓ | | | Create a new merge request starting from the current issue |
| `/relate #issue1 #issue2` | ✓ | | | Mark issues as related **(STARTER)** |
| `/move <path/to/project>` | ✓ | | | Move this issue to another project |
| `/zoom <Zoom URL>` | ✓ | | | Add Zoom meeting to this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/remove_zoom` | ✓ | | | Remove Zoom meeting from this issue. ([Introduced in GitLab 12.3](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/16609) enabled by feature flag `issue_zoom_integration`) |
| `/target_branch <local branch name>` | | ✓ | | Set target branch |
| `/wip` | | ✓ | | Toggle the Work In Progress status |
| `/approve` | | ✓ | | Approve the merge request |
Loading
Loading
Loading
Loading
@@ -31,6 +31,9 @@ module.exports = {
moduleNameMapper: {
'^~(/.*)$': '<rootDir>/app/assets/javascripts$1',
'^ee(/.*)$': '<rootDir>/ee/app/assets/javascripts$1',
'^ee_component(/.*)$': IS_EE
? '<rootDir>/ee/app/assets/javascripts$1'
: '<rootDir>/app/assets/javascripts/vue_shared/components/empty_component.js',
'^ee_else_ce(/.*)$': IS_EE
? '<rootDir>/ee/app/assets/javascripts$1'
: '<rootDir>/app/assets/javascripts$1',
Loading
Loading
Loading
Loading
@@ -167,6 +167,49 @@ module Gitlab
issue_iid: quick_action_target.iid
}
end
desc _('Add Zoom meeting')
explanation _('Adds a Zoom meeting')
params '<Zoom URL>'
types Issue
condition do
zoom_link_service.can_add_link?
end
parse_params do |link|
zoom_link_service.parse_link(link)
end
command :zoom do |link|
result = zoom_link_service.add_link(link)
if result.success?
@updates[:description] = result.payload[:description]
end
@execution_message[:zoom] = result.message
end
desc _('Remove Zoom meeting')
explanation _('Remove Zoom meeting')
execution_message _('Zoom meeting removed')
types Issue
condition do
zoom_link_service.can_remove_link?
end
command :remove_zoom do
result = zoom_link_service.remove_link
if result.success?
@updates[:description] = result.payload[:description]
end
@execution_message[:remove_zoom] = result.message
end
private
def zoom_link_service
Issues::ZoomLinkService.new(quick_action_target, current_user)
end
end
end
end
Loading
Loading
Loading
Loading
@@ -836,6 +836,9 @@ msgstr ""
msgid "Add README"
msgstr ""
 
msgid "Add Zoom meeting"
msgstr ""
msgid "Add a %{type} token"
msgstr ""
 
Loading
Loading
@@ -1007,6 +1010,9 @@ msgstr ""
msgid "Adds a To Do."
msgstr ""
 
msgid "Adds a Zoom meeting"
msgstr ""
msgid "Adds an issue to an epic."
msgstr ""
 
Loading
Loading
@@ -6268,6 +6274,9 @@ msgstr ""
msgid "Failed create wiki"
msgstr ""
 
msgid "Failed to add a Zoom meeting"
msgstr ""
msgid "Failed to apply commands."
msgstr ""
 
Loading
Loading
@@ -6340,6 +6349,9 @@ msgstr ""
msgid "Failed to protect the environment"
msgstr ""
 
msgid "Failed to remove a Zoom meeting"
msgstr ""
msgid "Failed to remove issue from board, please try again."
msgstr ""
 
Loading
Loading
@@ -12672,6 +12684,9 @@ msgstr ""
msgid "Remove Runner"
msgstr ""
 
msgid "Remove Zoom meeting"
msgstr ""
msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
msgstr ""
 
Loading
Loading
@@ -18118,6 +18133,12 @@ msgstr ""
msgid "Your request for access has been queued for review."
msgstr ""
 
msgid "Zoom meeting added"
msgstr ""
msgid "Zoom meeting removed"
msgstr ""
msgid "a deleted user"
msgstr ""
 
Loading
Loading
Loading
Loading
@@ -39,6 +39,7 @@ module QA
end
 
Page::Project::Issue::Show.perform do |show|
show.select_all_activities_filter
expect(show).to have_element(:reopen_issue_button)
expect(show).to have_content("closed via commit #{commit_sha}")
end
Loading
Loading
# frozen_string_literal: true
 
module QA
context 'Plan' do
context 'Plan', :smoke do
describe 'mention' do
let(:user) do
Resource::User.fabricate_via_api! do |user|
Loading
Loading
Loading
Loading
@@ -42,5 +42,6 @@ describe 'Issues > User uses quick actions', :js do
 
it_behaves_like 'create_merge_request quick action'
it_behaves_like 'move quick action'
it_behaves_like 'zoom quick actions'
end
end
import $ from 'helpers/jquery';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Vue from 'vue';
import { mount, createLocalVue } from '@vue/test-utils';
import NotesApp from '~/notes/components/notes_app.vue';
Loading
Loading
@@ -9,19 +11,10 @@ import { setTestTimeout } from 'helpers/timeout';
// TODO: use generated fixture (https://gitlab.com/gitlab-org/gitlab-ce/issues/62491)
import * as mockData from '../../../javascripts/notes/mock_data';
 
const originalInterceptors = [...Vue.http.interceptors];
const emptyResponseInterceptor = (request, next) => {
next(
request.respondWith(JSON.stringify([]), {
status: 200,
}),
);
};
setTestTimeout(1000);
 
describe('note_app', () => {
let axiosMock;
let mountComponent;
let wrapper;
let store;
Loading
Loading
@@ -45,6 +38,8 @@ describe('note_app', () => {
beforeEach(() => {
$('body').attr('data-page', 'projects:merge_requests:show');
 
axiosMock = new AxiosMockAdapter(axios);
store = createStore();
mountComponent = data => {
const propsData = data || {
Loading
Loading
@@ -74,12 +69,12 @@ describe('note_app', () => {
 
afterEach(() => {
wrapper.destroy();
Vue.http.interceptors = [...originalInterceptors];
axiosMock.restore();
});
 
describe('set data', () => {
beforeEach(() => {
Vue.http.interceptors.push(emptyResponseInterceptor);
axiosMock.onAny().reply(200, []);
wrapper = mountComponent();
return waitForDiscussionsRequest();
});
Loading
Loading
@@ -105,7 +100,7 @@ describe('note_app', () => {
beforeEach(() => {
setFixtures('<div class="js-discussions-count"></div>');
 
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
wrapper = mountComponent();
return waitForDiscussionsRequest();
});
Loading
Loading
@@ -146,7 +141,7 @@ describe('note_app', () => {
beforeEach(() => {
setFixtures('<div class="js-discussions-count"></div>');
 
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
store.state.commentsDisabled = true;
wrapper = mountComponent();
return waitForDiscussionsRequest();
Loading
Loading
@@ -163,7 +158,7 @@ describe('note_app', () => {
 
describe('while fetching data', () => {
beforeEach(() => {
Vue.http.interceptors.push(emptyResponseInterceptor);
axiosMock.onAny().reply(200, []);
wrapper = mountComponent();
});
 
Loading
Loading
@@ -184,7 +179,7 @@ describe('note_app', () => {
describe('update note', () => {
describe('individual note', () => {
beforeEach(() => {
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
jest.spyOn(service, 'updateNote');
wrapper = mountComponent();
return waitForDiscussionsRequest().then(() => {
Loading
Loading
@@ -206,7 +201,7 @@ describe('note_app', () => {
 
describe('discussion note', () => {
beforeEach(() => {
Vue.http.interceptors.push(mockData.discussionNoteInterceptor);
axiosMock.onAny().reply(mockData.getDiscussionNoteResponse);
jest.spyOn(service, 'updateNote');
wrapper = mountComponent();
return waitForDiscussionsRequest().then(() => {
Loading
Loading
@@ -229,7 +224,7 @@ describe('note_app', () => {
 
describe('new note form', () => {
beforeEach(() => {
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
wrapper = mountComponent();
return waitForDiscussionsRequest();
});
Loading
Loading
@@ -259,7 +254,7 @@ describe('note_app', () => {
 
describe('edit form', () => {
beforeEach(() => {
Vue.http.interceptors.push(mockData.individualNoteInterceptor);
axiosMock.onAny().reply(mockData.getIndividualNoteResponse);
wrapper = mountComponent();
return waitForDiscussionsRequest();
});
Loading
Loading
@@ -287,7 +282,7 @@ describe('note_app', () => {
 
describe('emoji awards', () => {
beforeEach(() => {
Vue.http.interceptors.push(emptyResponseInterceptor);
axiosMock.onAny().reply(200, []);
wrapper = mountComponent();
return waitForDiscussionsRequest();
});
Loading
Loading
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import MilestoneList from '~/releases/components/milestone_list.vue';
import Icon from '~/vue_shared/components/icon.vue';
import _ from 'underscore';
import { milestones } from '../mock_data';
describe('Milestone list', () => {
let wrapper;
const factory = milestonesProp => {
wrapper = shallowMount(MilestoneList, {
propsData: {
milestones: milestonesProp,
},
sync: false,
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders the milestone icon', () => {
factory(milestones);
expect(wrapper.find(Icon).exists()).toBe(true);
});
it('renders the label as "Milestone" if only a single milestone is passed in', () => {
factory(milestones.slice(0, 1));
expect(wrapper.find('.js-label-text').text()).toEqual('Milestone');
});
it('renders the label as "Milestones" if more than one milestone is passed in', () => {
factory(milestones);
expect(wrapper.find('.js-label-text').text()).toEqual('Milestones');
});
it('renders a link to the milestone with a tooltip', () => {
const milestone = _.first(milestones);
factory([milestone]);
const milestoneLink = wrapper.find(GlLink);
expect(milestoneLink.exists()).toBe(true);
expect(milestoneLink.text()).toBe(milestone.title);
expect(milestoneLink.attributes('href')).toBe(milestone.web_url);
expect(milestoneLink.attributes('data-original-title')).toBe(milestone.description);
});
});
import { mount } from '@vue/test-utils';
import ReleaseBlock from '~/releases/components/release_block.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { first } from 'underscore';
import { release } from '../mock_data';
describe('Release block', () => {
let wrapper;
const factory = releaseProp => {
wrapper = mount(ReleaseBlock, {
propsData: {
release: releaseProp,
},
sync: false,
});
};
const milestoneListExists = () => wrapper.find('.js-milestone-list').exists();
afterEach(() => {
wrapper.destroy();
});
describe('with default props', () => {
beforeEach(() => {
factory(release);
});
it("renders the block with an id equal to the release's tag name", () => {
expect(wrapper.attributes().id).toBe('v0.3');
});
it('renders release name', () => {
expect(wrapper.text()).toContain(release.name);
});
it('renders commit sha', () => {
expect(wrapper.text()).toContain(release.commit.short_id);
});
it('renders tag name', () => {
expect(wrapper.text()).toContain(release.tag_name);
});
it('renders release date', () => {
expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormated(release.released_at));
});
it('renders number of assets provided', () => {
expect(wrapper.find('.js-assets-count').text()).toContain(release.assets.count);
});
it('renders dropdown with the sources', () => {
expect(wrapper.findAll('.js-sources-dropdown li').length).toEqual(
release.assets.sources.length,
);
expect(wrapper.find('.js-sources-dropdown li a').attributes().href).toEqual(
first(release.assets.sources).url,
);
expect(wrapper.find('.js-sources-dropdown li a').text()).toContain(
first(release.assets.sources).format,
);
});
it('renders list with the links provided', () => {
expect(wrapper.findAll('.js-assets-list li').length).toEqual(release.assets.links.length);
expect(wrapper.find('.js-assets-list li a').attributes().href).toEqual(
first(release.assets.links).url,
);
expect(wrapper.find('.js-assets-list li a').text()).toContain(
first(release.assets.links).name,
);
});
it('renders author avatar', () => {
expect(wrapper.find('.user-avatar-link').exists()).toBe(true);
});
describe('external label', () => {
it('renders external label when link is external', () => {
expect(wrapper.find('.js-assets-list li a').text()).toContain('external source');
});
it('does not render external label when link is not external', () => {
expect(wrapper.find('.js-assets-list li:nth-child(2) a').text()).not.toContain(
'external source',
);
});
});
it('renders the milestone list if at least one milestone is associated to the release', () => {
factory(release);
expect(milestoneListExists()).toBe(true);
});
});
it('does not render the milestone list if no milestones are associated to the release', () => {
const releaseClone = JSON.parse(JSON.stringify(release));
delete releaseClone.milestone;
factory(releaseClone);
expect(milestoneListExists()).toBe(false);
});
it('renders upcoming release badge', () => {
const releaseClone = JSON.parse(JSON.stringify(release));
releaseClone.upcoming_release = true;
factory(releaseClone);
expect(wrapper.text()).toContain('Upcoming Release');
});
});
export const milestones = [
{
id: 50,
iid: 2,
project_id: 18,
title: '13.6',
description: 'The 13.6 milestone!',
state: 'active',
created_at: '2019-08-27T17:22:38.280Z',
updated_at: '2019-08-27T17:22:38.280Z',
due_date: '2019-09-19',
start_date: '2019-08-31',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2',
},
{
id: 49,
iid: 1,
project_id: 18,
title: '13.5',
description: 'The 13.5 milestone!',
state: 'active',
created_at: '2019-08-26T17:55:48.643Z',
updated_at: '2019-08-26T17:55:48.643Z',
due_date: '2019-10-11',
start_date: '2019-08-19',
web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1',
},
];
export const release = {
name: 'New release',
tag_name: 'v0.3',
description: 'A super nice release!',
description_html: '<p data-sourcepos="1:1-1:21" dir="auto">A super nice release!</p>',
created_at: '2019-08-26T17:54:04.952Z',
released_at: '2019-08-26T17:54:04.807Z',
author: {
id: 1,
name: 'Administrator',
username: 'root',
state: 'active',
avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
web_url: 'http://0.0.0.0:3001/root',
},
commit: {
id: 'c22b0728d1b465f82898c884d32b01aa642f96c1',
short_id: 'c22b0728',
created_at: '2019-08-26T17:47:07.000Z',
parent_ids: [],
title: 'Initial commit',
message: 'Initial commit',
author_name: 'Administrator',
author_email: 'admin@example.com',
authored_date: '2019-08-26T17:47:07.000Z',
committer_name: 'Administrator',
committer_email: 'admin@example.com',
committed_date: '2019-08-26T17:47:07.000Z',
},
upcoming_release: false,
milestone: milestones[0],
assets: {
count: 5,
sources: [
{
format: 'zip',
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.zip',
},
{
format: 'tar.gz',
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.gz',
},
{
format: 'tar.bz2',
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.bz2',
},
{
format: 'tar',
url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar',
},
],
links: [
{
id: 1,
name: 'my link',
url: 'https://google.com',
external: true,
},
{
id: 2,
name: 'my second link',
url:
'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
external: false,
},
],
},
};
Loading
Loading
@@ -647,24 +647,12 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = {
},
};
 
export function individualNoteInterceptor(request, next) {
const body = INDIVIDUAL_NOTE_RESPONSE_MAP[request.method.toUpperCase()][request.url];
next(
request.respondWith(JSON.stringify(body), {
status: 200,
}),
);
export function getIndividualNoteResponse(config) {
return [200, INDIVIDUAL_NOTE_RESPONSE_MAP[config.method.toUpperCase()][config.url]];
}
 
export function discussionNoteInterceptor(request, next) {
const body = DISCUSSION_NOTE_RESPONSE_MAP[request.method.toUpperCase()][request.url];
next(
request.respondWith(JSON.stringify(body), {
status: 200,
}),
);
export function getDiscussionNoteResponse(config) {
return [200, DISCUSSION_NOTE_RESPONSE_MAP[config.method.toUpperCase()][config.url]];
}
 
export const notesWithDescriptionChanges = [
Loading
Loading
import Vue from 'vue';
import $ from 'jquery';
import _ from 'underscore';
import Api from '~/api';
import { TEST_HOST } from 'spec/test_constants';
import { headersInterceptor } from 'spec/helpers/vue_resource_helper';
import actionsModule, * as actions from '~/notes/stores/actions';
import * as mutationTypes from '~/notes/stores/mutation_types';
import * as notesConstants from '~/notes/constants';
Loading
Loading
@@ -29,6 +26,7 @@ describe('Actions Notes Store', () => {
let state;
let store;
let flashSpy;
let axiosMock;
 
beforeEach(() => {
store = createStore();
Loading
Loading
@@ -36,10 +34,12 @@ describe('Actions Notes Store', () => {
dispatch = jasmine.createSpy('dispatch');
state = {};
flashSpy = spyOnDependency(actionsModule, 'Flash');
axiosMock = new AxiosMockAdapter(axios);
});
 
afterEach(() => {
resetStore(store);
axiosMock.restore();
});
 
describe('setNotesData', () => {
Loading
Loading
@@ -160,20 +160,8 @@ describe('Actions Notes Store', () => {
});
 
describe('async methods', () => {
const interceptor = (request, next) => {
next(
request.respondWith(JSON.stringify({}), {
status: 200,
}),
);
};
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
axiosMock.onAny().reply(200, {});
});
 
describe('closeIssue', () => {
Loading
Loading
@@ -259,7 +247,7 @@ describe('Actions Notes Store', () => {
beforeEach(done => {
jasmine.clock().install();
 
spyOn(Vue.http, 'get').and.callThrough();
spyOn(axios, 'get').and.callThrough();
 
store
.dispatch('setNotesData', notesDataMock)
Loading
Loading
@@ -272,31 +260,15 @@ describe('Actions Notes Store', () => {
});
 
it('calls service with last fetched state', done => {
const interceptor = (request, next) => {
next(
request.respondWith(
JSON.stringify({
notes: [],
last_fetched_at: '123456',
}),
{
status: 200,
headers: {
'poll-interval': '1000',
},
},
),
);
};
Vue.http.interceptors.push(interceptor);
Vue.http.interceptors.push(headersInterceptor);
axiosMock
.onAny()
.reply(200, { notes: [], last_fetched_at: '123456' }, { 'poll-interval': '1000' });
 
store
.dispatch('poll')
.then(() => new Promise(resolve => requestAnimationFrame(resolve)))
.then(() => {
expect(Vue.http.get).toHaveBeenCalled();
expect(axios.get).toHaveBeenCalled();
expect(store.state.lastFetchedAt).toBe('123456');
 
jasmine.clock().tick(1500);
Loading
Loading
@@ -308,16 +280,12 @@ describe('Actions Notes Store', () => {
}),
)
.then(() => {
expect(Vue.http.get.calls.count()).toBe(2);
expect(Vue.http.get.calls.mostRecent().args[1].headers).toEqual({
expect(axios.get.calls.count()).toBe(2);
expect(axios.get.calls.mostRecent().args[1].headers).toEqual({
'X-Last-Fetched-At': '123456',
});
})
.then(() => store.dispatch('stopPolling'))
.then(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
Vue.http.interceptors = _.without(Vue.http.interceptors, headersInterceptor);
})
.then(done)
.catch(done.fail);
});
Loading
Loading
@@ -338,10 +306,8 @@ describe('Actions Notes Store', () => {
 
describe('removeNote', () => {
const endpoint = `${TEST_HOST}/note`;
let axiosMock;
 
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
axiosMock.onDelete(endpoint).replyOnce(200, {});
 
$('body').attr('data-page', '');
Loading
Loading
@@ -411,10 +377,8 @@ describe('Actions Notes Store', () => {
 
describe('deleteNote', () => {
const endpoint = `${TEST_HOST}/note`;
let axiosMock;
 
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
axiosMock.onDelete(endpoint).replyOnce(200, {});
 
$('body').attr('data-page', '');
Loading
Loading
@@ -454,20 +418,9 @@ describe('Actions Notes Store', () => {
id: 1,
valid: true,
};
const interceptor = (request, next) => {
next(
request.respondWith(JSON.stringify(res), {
status: 200,
}),
);
};
 
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
axiosMock.onAny().reply(200, res);
});
 
it('commits ADD_NEW_NOTE and dispatches updateMergeRequestWidget', done => {
Loading
Loading
@@ -501,20 +454,9 @@ describe('Actions Notes Store', () => {
const res = {
errors: ['error'],
};
const interceptor = (request, next) => {
next(
request.respondWith(JSON.stringify(res), {
status: 200,
}),
);
};
 
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
axiosMock.onAny().replyOnce(200, res);
});
 
it('does not commit ADD_NEW_NOTE or dispatch updateMergeRequestWidget', done => {
Loading
Loading
@@ -534,20 +476,9 @@ describe('Actions Notes Store', () => {
const res = {
resolved: true,
};
const interceptor = (request, next) => {
next(
request.respondWith(JSON.stringify(res), {
status: 200,
}),
);
};
 
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
axiosMock.onAny().reply(200, res);
});
 
describe('as note', () => {
Loading
Loading
@@ -720,32 +651,19 @@ describe('Actions Notes Store', () => {
});
 
describe('replyToDiscussion', () => {
let res = { discussion: { notes: [] } };
const payload = { endpoint: TEST_HOST, data: {} };
const interceptor = (request, next) => {
next(
request.respondWith(JSON.stringify(res), {
status: 200,
}),
);
};
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
});
 
it('updates discussion if response contains disussion', done => {
const discussion = { notes: [] };
axiosMock.onAny().reply(200, { discussion });
testAction(
actions.replyToDiscussion,
payload,
{
notesById: {},
},
[{ type: mutationTypes.UPDATE_DISCUSSION, payload: res.discussion }],
[{ type: mutationTypes.UPDATE_DISCUSSION, payload: discussion }],
[
{ type: 'updateMergeRequestWidget' },
{ type: 'startTaskList' },
Loading
Loading
@@ -756,7 +674,8 @@ describe('Actions Notes Store', () => {
});
 
it('adds a reply to a discussion', done => {
res = {};
const res = {};
axiosMock.onAny().reply(200, res);
 
testAction(
actions.replyToDiscussion,
Loading
Loading
import Vue from 'vue';
import component from '~/releases/components/release_block.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Release block', () => {
const Component = Vue.extend(component);
const release = {
name: 'Bionic Beaver',
tag_name: '18.04',
description: '## changelog\n\n* line 1\n* line2',
description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>',
author_name: 'Release bot',
author_email: 'release-bot@example.com',
released_at: '2012-05-28T05:00:00-07:00',
author: {
avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png',
id: 482476,
name: 'John Doe',
path: '/johndoe',
state: 'active',
status_tooltip_html: null,
username: 'johndoe',
web_url: 'https://gitlab.com/johndoe',
},
commit: {
id: '2695effb5807a22ff3d138d593fd856244e155e7',
short_id: '2695effb',
title: 'Initial commit',
created_at: '2017-07-26T11:08:53.000+02:00',
parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'],
message: 'Initial commit',
author_name: 'John Smith',
author_email: 'john@example.com',
authored_date: '2012-05-28T04:42:42-07:00',
committer_name: 'Jack Smith',
committer_email: 'jack@example.com',
committed_date: '2012-05-28T04:42:42-07:00',
},
assets: {
count: 6,
sources: [
{
format: 'zip',
url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.zip',
},
{
format: 'tar.gz',
url:
'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.gz',
},
{
format: 'tar.bz2',
url:
'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar.bz2',
},
{
format: 'tar',
url: 'https://gitlab.com/gitlab-org/gitlab-ce/-/archive/v11.3.12/gitlab-ce-v11.3.12.tar',
},
],
links: [
{
name: 'release-18.04.dmg',
url: 'https://my-external-hosting.example.com/scrambled-url/',
external: true,
},
{
name: 'binary-linux-amd64',
url:
'https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50',
external: false,
},
],
},
};
let vm;
const factory = props => mountComponent(Component, { release: props });
beforeEach(() => {
vm = factory(release);
});
afterEach(() => {
vm.$destroy();
});
it("renders the block with an id equal to the release's tag name", () => {
expect(vm.$el.id).toBe('18.04');
});
it('renders release name', () => {
expect(vm.$el.textContent).toContain(release.name);
});
it('renders commit sha', () => {
expect(vm.$el.textContent).toContain(release.commit.short_id);
});
it('renders tag name', () => {
expect(vm.$el.textContent).toContain(release.tag_name);
});
it('renders release date', () => {
expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at));
});
it('renders number of assets provided', () => {
expect(vm.$el.querySelector('.js-assets-count').textContent).toContain(release.assets.count);
});
it('renders dropdown with the sources', () => {
expect(vm.$el.querySelectorAll('.js-sources-dropdown li').length).toEqual(
release.assets.sources.length,
);
expect(vm.$el.querySelector('.js-sources-dropdown li a').getAttribute('href')).toEqual(
release.assets.sources[0].url,
);
expect(vm.$el.querySelector('.js-sources-dropdown li a').textContent).toContain(
release.assets.sources[0].format,
);
});
it('renders list with the links provided', () => {
expect(vm.$el.querySelectorAll('.js-assets-list li').length).toEqual(
release.assets.links.length,
);
expect(vm.$el.querySelector('.js-assets-list li a').getAttribute('href')).toEqual(
release.assets.links[0].url,
);
expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain(
release.assets.links[0].name,
);
});
it('renders author avatar', () => {
expect(vm.$el.querySelector('.user-avatar-link')).not.toBeNull();
});
describe('external label', () => {
it('renders external label when link is external', () => {
expect(vm.$el.querySelector('.js-assets-list li a').textContent).toContain('external source');
});
it('does not render external label when link is not external', () => {
expect(vm.$el.querySelector('.js-assets-list li:nth-child(2) a').textContent).not.toContain(
'external source',
);
});
});
describe('with upcoming_release flag', () => {
beforeEach(() => {
vm = factory(Object.assign({}, release, { upcoming_release: true }));
});
it('renders upcoming release badge', () => {
expect(vm.$el.textContent).toContain('Upcoming Release');
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment