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';
import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation';
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 eventHub from '../event_hub';
......@@ -46,6 +47,7 @@ export default {
ReplyPlaceholder,
placeholderNote,
placeholderSystemNote,
ResolveWithIssueButton,
systemNote,
DraftNote,
TimelineEntryItem,
......@@ -244,6 +246,9 @@ export default {
url: this.discussion.discussion_path,
};
},
resolveWithIssuePath() {
return !this.discussionResolved && this.discussion.resolve_with_issue_path;
},
},
watch: {
isReplying() {
......@@ -502,16 +507,10 @@ Please check your network connection and try again.`;
class="btn-group discussion-actions ml-sm-2"
role="group"
>
<div v-if="!discussionResolved" class="btn-group" role="group">
<a
v-gl-tooltip
: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>
<resolve-with-issue-button
v-if="resolveWithIssuePath"
:url="resolveWithIssuePath"
/>
<jump-to-next-discussion-button
v-if="shouldShowJumpToNextDiscussion"
@onClick="jumpToNextDiscussion"
......
......@@ -54,6 +54,12 @@ export default {
deployTimeago() {
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() {
return !!(this.deployment.external_url && this.deployment.external_url_formatted);
},
......@@ -78,7 +84,7 @@ export default {
: '';
},
shouldRenderDropdown() {
return this.deployment.changes && this.deployment.changes.length > 0;
return this.deployment.changes && this.deployment.changes.length > 1;
},
showMemoryUsage() {
return this.hasMetrics && this.showMetrics;
......@@ -154,12 +160,12 @@ export default {
v-if="shouldRenderDropdown"
class="js-mr-wigdet-deployment-dropdown inline"
:items="deployment.changes"
:main-action-link="deployment.external_url"
:main-action-link="deploymentExternalUrl"
filter-key="path"
>
<template slot="mainAction" slot-scope="slotProps">
<review-app-link
:link="deployment.external_url"
:link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/>
</template>
......@@ -183,7 +189,7 @@ export default {
</filtered-search-dropdown>
<review-app-link
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"
/>
</template>
......
......@@ -45,7 +45,14 @@ class Projects::GraphsController < Projects::ApplicationController
end
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
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
desc 'Get languages in project repository'
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
desc 'Remove a project'
......
......@@ -24,4 +24,20 @@ describe Projects::GraphsController do
expect(response).to redirect_to action: :charts
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
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';
import createStore from '~/notes/stores';
import noteableDiscussion from '~/notes/components/noteable_discussion.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 { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
import mockDiffFile from '../../diffs/mock_data/diff_file';
......@@ -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';
describe('Deployment component', () => {
const Component = Vue.extend(deploymentComponent);
const deploymentMockData = {
id: 15,
name: 'review/diplo',
url: '/root/review-apps/environments/15',
stop_url: '/root/review-apps/environments/15/stop',
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
external_url: 'http://gitlab.com.',
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes: [
{
path: 'index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
},
{
path: 'imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
},
{
path: 'about/',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
let deploymentMockData;
beforeEach(() => {
deploymentMockData = {
id: 15,
name: 'review/diplo',
url: '/root/review-apps/environments/15',
stop_url: '/root/review-apps/environments/15/stop',
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
external_url: 'http://gitlab.com.',
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes: [
{
path: 'index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
},
{
path: 'imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
},
{
path: 'about/',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
});
let vm;
......@@ -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('running', () => {
beforeEach(() => {
......
......@@ -4,6 +4,15 @@ require 'spec_helper'
shared_examples 'languages and percentages JSON response' do
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
get api("/projects/#{project.id}/languages", user)
......@@ -11,6 +20,23 @@ shared_examples 'languages and percentages JSON response' do
expect(json_response).to eq(expected_languages)
expect(json_response.count).to be > 1
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
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