Commit 59b301b1 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents c6af4aff 0b0a04fd
<script>
import Icon from '~/vue_shared/components/icon.vue';
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
export default {
name: 'ResolveWithIssueButton',
components: {
Icon,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
url: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="btn-group" role="group">
<gl-button
v-gl-tooltip
:href="url"
:title="s__('MergeRequests|Resolve this discussion in a new issue')"
class="new-issue-for-discussion discussion-create-issue-btn"
>
<icon name="issue-new" />
</gl-button>
</div>
</template>
...@@ -27,6 +27,7 @@ import noteable from '../mixins/noteable'; ...@@ -27,6 +27,7 @@ import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable'; import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation'; import discussionNavigation from '../mixins/discussion_navigation';
import ReplyPlaceholder from './discussion_reply_placeholder.vue'; import ReplyPlaceholder from './discussion_reply_placeholder.vue';
import ResolveWithIssueButton from './discussion_resolve_with_issue_button.vue';
import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue'; import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -46,6 +47,7 @@ export default { ...@@ -46,6 +47,7 @@ export default {
ReplyPlaceholder, ReplyPlaceholder,
placeholderNote, placeholderNote,
placeholderSystemNote, placeholderSystemNote,
ResolveWithIssueButton,
systemNote, systemNote,
DraftNote, DraftNote,
TimelineEntryItem, TimelineEntryItem,
...@@ -244,6 +246,9 @@ export default { ...@@ -244,6 +246,9 @@ export default {
url: this.discussion.discussion_path, url: this.discussion.discussion_path,
}; };
}, },
resolveWithIssuePath() {
return !this.discussionResolved && this.discussion.resolve_with_issue_path;
},
}, },
watch: { watch: {
isReplying() { isReplying() {
...@@ -502,16 +507,10 @@ Please check your network connection and try again.`; ...@@ -502,16 +507,10 @@ Please check your network connection and try again.`;
class="btn-group discussion-actions ml-sm-2" class="btn-group discussion-actions ml-sm-2"
role="group" role="group"
> >
<div v-if="!discussionResolved" class="btn-group" role="group"> <resolve-with-issue-button
<a v-if="resolveWithIssuePath"
v-gl-tooltip :url="resolveWithIssuePath"
:href="discussion.resolve_with_issue_path" />
:title="s__('MergeRequests|Resolve this discussion in a new issue')"
class="new-issue-for-discussion btn btn-default discussion-create-issue-btn"
>
<icon name="issue-new" />
</a>
</div>
<jump-to-next-discussion-button <jump-to-next-discussion-button
v-if="shouldShowJumpToNextDiscussion" v-if="shouldShowJumpToNextDiscussion"
@onClick="jumpToNextDiscussion" @onClick="jumpToNextDiscussion"
......
...@@ -54,6 +54,12 @@ export default { ...@@ -54,6 +54,12 @@ export default {
deployTimeago() { deployTimeago() {
return this.timeFormated(this.deployment.deployed_at); return this.timeFormated(this.deployment.deployed_at);
}, },
deploymentExternalUrl() {
if (this.deployment.changes && this.deployment.changes.length === 1) {
return this.deployment.changes[0].external_url;
}
return this.deployment.external_url;
},
hasExternalUrls() { hasExternalUrls() {
return !!(this.deployment.external_url && this.deployment.external_url_formatted); return !!(this.deployment.external_url && this.deployment.external_url_formatted);
}, },
...@@ -78,7 +84,7 @@ export default { ...@@ -78,7 +84,7 @@ export default {
: ''; : '';
}, },
shouldRenderDropdown() { shouldRenderDropdown() {
return this.deployment.changes && this.deployment.changes.length > 0; return this.deployment.changes && this.deployment.changes.length > 1;
}, },
showMemoryUsage() { showMemoryUsage() {
return this.hasMetrics && this.showMetrics; return this.hasMetrics && this.showMetrics;
...@@ -154,12 +160,12 @@ export default { ...@@ -154,12 +160,12 @@ export default {
v-if="shouldRenderDropdown" v-if="shouldRenderDropdown"
class="js-mr-wigdet-deployment-dropdown inline" class="js-mr-wigdet-deployment-dropdown inline"
:items="deployment.changes" :items="deployment.changes"
:main-action-link="deployment.external_url" :main-action-link="deploymentExternalUrl"
filter-key="path" filter-key="path"
> >
<template slot="mainAction" slot-scope="slotProps"> <template slot="mainAction" slot-scope="slotProps">
<review-app-link <review-app-link
:link="deployment.external_url" :link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`" :css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/> />
</template> </template>
...@@ -183,7 +189,7 @@ export default { ...@@ -183,7 +189,7 @@ export default {
</filtered-search-dropdown> </filtered-search-dropdown>
<review-app-link <review-app-link
v-else v-else
:link="deployment.external_url" :link="deploymentExternalUrl"
css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inlin" css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inlin"
/> />
</template> </template>
......
...@@ -45,7 +45,14 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -45,7 +45,14 @@ class Projects::GraphsController < Projects::ApplicationController
end end
def get_languages def get_languages
@languages = @project.repository.languages @languages =
if @project.repository_languages.present?
@project.repository_languages.map do |lang|
{ value: lang.share, label: lang.name, color: lang.color, highlight: lang.color }
end
else
@project.repository.languages
end
end end
def fetch_graph def fetch_graph
......
---
title: Review App Link to Changed Page if Only One Change Present
merge_request: 25048
author:
type: changed
---
title: Extracted ResolveWithIssueButton to its own component
merge_request: 25093
author: Martin Hobert
type: other
---
title: Load repository language from the database if detected before
merge_request: 25518
author:
type: performance
...@@ -391,7 +391,11 @@ module API ...@@ -391,7 +391,11 @@ module API
desc 'Get languages in project repository' desc 'Get languages in project repository'
get ':id/languages' do get ':id/languages' do
user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h if user_project.repository_languages.present?
user_project.repository_languages.map { |l| [l.name, l.share] }.to_h
else
user_project.repository.languages.map { |language| language.values_at(:label, :value) }.to_h
end
end end
desc 'Remove a project' desc 'Remove a project'
......
...@@ -24,4 +24,20 @@ describe Projects::GraphsController do ...@@ -24,4 +24,20 @@ describe Projects::GraphsController do
expect(response).to redirect_to action: :charts expect(response).to redirect_to action: :charts
end end
end end
describe 'charts' do
context 'when languages were previously detected' do
let!(:repository_language) { create(:repository_language, project: project) }
it 'sets the languages properly' do
get(:charts, params: { namespace_id: project.namespace.path, project_id: project.path, id: 'master' })
expect(assigns[:languages]).to eq(
[value: repository_language.share,
label: repository_language.name,
color: repository_language.color,
highlight: repository_language.color])
end
end
end
end end
import { GlButton } from '@gitlab/ui';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants';
const localVue = createLocalVue();
describe('ResolveWithIssueButton', () => {
let wrapper;
const url = `${TEST_HOST}/hello-world/`;
beforeEach(() => {
wrapper = shallowMount(ResolveWithIssueButton, {
localVue,
sync: false,
propsData: {
url,
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('it should have a link with the provided link property as href', () => {
const button = wrapper.find(GlButton);
expect(button.attributes().href).toBe(url);
});
});
...@@ -2,6 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; ...@@ -2,6 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores'; import createStore from '~/notes/stores';
import noteableDiscussion from '~/notes/components/noteable_discussion.vue'; import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
import '~/behaviors/markdown/render_gfm'; import '~/behaviors/markdown/render_gfm';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
import mockDiffFile from '../../diffs/mock_data/diff_file'; import mockDiffFile from '../../diffs/mock_data/diff_file';
...@@ -238,4 +239,42 @@ describe('noteable_discussion component', () => { ...@@ -238,4 +239,42 @@ describe('noteable_discussion component', () => {
}); });
}); });
}); });
describe('for resolved discussion', () => {
beforeEach(() => {
const discussion = getJSONFixture(discussionWithTwoUnresolvedNotes)[0];
wrapper.setProps({ discussion });
});
it('does not display a button to resolve with issue', () => {
const button = wrapper.find(ResolveWithIssueButton);
expect(button.exists()).toBe(false);
});
});
describe('for unresolved discussion', () => {
beforeEach(done => {
const discussion = {
...getJSONFixture(discussionWithTwoUnresolvedNotes)[0],
expanded: true,
};
discussion.notes = discussion.notes.map(note => ({
...note,
resolved: false,
}));
wrapper.setProps({ discussion });
wrapper.vm
.$nextTick()
.then(done)
.catch(done.fail);
});
it('displays a button to resolve with issue', () => {
const button = wrapper.find(ResolveWithIssueButton);
expect(button.exists()).toBe(true);
});
});
}); });
...@@ -6,32 +6,36 @@ import mountComponent from '../../helpers/vue_mount_component_helper'; ...@@ -6,32 +6,36 @@ import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Deployment component', () => { describe('Deployment component', () => {
const Component = Vue.extend(deploymentComponent); const Component = Vue.extend(deploymentComponent);
const deploymentMockData = { let deploymentMockData;
id: 15,
name: 'review/diplo', beforeEach(() => {
url: '/root/review-apps/environments/15', deploymentMockData = {
stop_url: '/root/review-apps/environments/15/stop', id: 15,
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics', name: 'review/diplo',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics', url: '/root/review-apps/environments/15',
external_url: 'http://gitlab.com.', stop_url: '/root/review-apps/environments/15/stop',
external_url_formatted: 'gitlab', metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
deployed_at: '2017-03-22T22:44:42.258Z', metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
deployed_at_formatted: 'Mar 22, 2017 10:44pm', external_url: 'http://gitlab.com.',
changes: [ external_url_formatted: 'gitlab',
{ deployed_at: '2017-03-22T22:44:42.258Z',
path: 'index.html', deployed_at_formatted: 'Mar 22, 2017 10:44pm',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html', changes: [
}, {
{ path: 'index.html',
path: 'imgs/gallery.html', external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html', },
}, {
{ path: 'imgs/gallery.html',
path: 'about/', external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/', },
}, {
], path: 'about/',
}; external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
});
let vm; let vm;
...@@ -207,6 +211,31 @@ describe('Deployment component', () => { ...@@ -207,6 +211,31 @@ describe('Deployment component', () => {
}); });
}); });
describe('with a single change', () => {
beforeEach(() => {
deploymentMockData.changes = deploymentMockData.changes.slice(0, 1);
vm = mountComponent(Component, {
deployment: { ...deploymentMockData },
showMetrics: true,
});
});
it('renders the link to the review app without dropdown', () => {
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull();
});
it('renders the link to the review app linked to to the first change', () => {
const expectedUrl = deploymentMockData.changes[0].external_url;
const deployUrl = vm.$el.querySelector('.js-deploy-url-feature-flag');
expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull();
expect(deployUrl).not.toBeNull();
expect(deployUrl.href).toEqual(expectedUrl);
});
});
describe('deployment status', () => { describe('deployment status', () => {
describe('running', () => { describe('running', () => {
beforeEach(() => { beforeEach(() => {
......
...@@ -4,6 +4,15 @@ require 'spec_helper' ...@@ -4,6 +4,15 @@ require 'spec_helper'
shared_examples 'languages and percentages JSON response' do shared_examples 'languages and percentages JSON response' do
let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h } let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h }
before do
allow(project.repository).to receive(:languages).and_return(
[{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" },
{ value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" },
{ value: 7.91, label: "HTML", color: "#e34c26", highlight: "#e34c26" },
{ value: 2.42, label: "CoffeeScript", color: "#244776", highlight: "#244776" }]
)
end
it 'returns expected language values' do it 'returns expected language values' do
get api("/projects/#{project.id}/languages", user) get api("/projects/#{project.id}/languages", user)
...@@ -11,6 +20,23 @@ shared_examples 'languages and percentages JSON response' do ...@@ -11,6 +20,23 @@ shared_examples 'languages and percentages JSON response' do
expect(json_response).to eq(expected_languages) expect(json_response).to eq(expected_languages)
expect(json_response.count).to be > 1 expect(json_response.count).to be > 1
end end
context 'when the languages were detected before' do
before do
Projects::DetectRepositoryLanguagesService.new(project, project.owner).execute
end
it 'returns the detection from the database' do
# Allow this to happen once, so the expected languages can be determined
expect(project.repository).to receive(:languages).once
get api("/projects/#{project.id}/languages", user)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(expected_languages)
expect(json_response.count).to be > 1
end
end
end end
describe API::Projects do describe API::Projects do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment