Commit f8d8b80c authored by Cornelius Ludmann's avatar Cornelius Ludmann Committed by Kerri Miller

Add Gitpod button to MR page

parent b011b9db
......@@ -14,6 +14,7 @@ import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
import MrWidgetHowToMergeModal from './mr_widget_how_to_merge_modal.vue';
import MrWidgetIcon from './mr_widget_icon.vue';
......@@ -30,6 +31,7 @@ export default {
GlDropdownItem,
GlLink,
GlSprintf,
WebIdeLink,
},
directives: {
GlTooltip: GlTooltipDirective,
......@@ -56,31 +58,24 @@ export default {
});
},
webIdePath() {
if (this.mr.canPushToSourceBranch) {
return mergeUrlParams(
{
target_project:
this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath
? this.mr.targetProjectFullPath
: '',
},
webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`),
);
}
return null;
},
ideButtonTitle() {
return !this.mr.canPushToSourceBranch
? s__(
'mrWidget|You are not allowed to edit this project directly. Please fork to make changes.',
)
: '';
return mergeUrlParams(
{
target_project:
this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath
? this.mr.targetProjectFullPath
: '',
},
webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`),
);
},
isFork() {
return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath;
},
},
i18n: {
webIdeText: s__('mrWidget|Open in Web IDE'),
gitpodText: s__('mrWidget|Open in Gitpod'),
},
};
</script>
<template>
......@@ -123,22 +118,21 @@ export default {
<div class="branch-actions d-flex">
<template v-if="mr.isOpen">
<span
<web-ide-link
v-if="!mr.sourceBranchRemoved"
v-gl-tooltip
:title="ideButtonTitle"
class="gl-display-none d-md-inline-block gl-mr-3"
:tabindex="ideButtonTitle ? 0 : null"
>
<gl-button
:href="webIdePath"
:disabled="!mr.canPushToSourceBranch"
class="js-web-ide"
data-qa-selector="open_in_web_ide_button"
>
{{ s__('mrWidget|Open in Web IDE') }}
</gl-button>
</span>
:show-edit-button="false"
:show-web-ide-button="true"
:web-ide-url="webIdePath"
:web-ide-text="$options.i18n.webIdeText"
:show-gitpod-button="mr.showGitpodButton"
:gitpod-url="mr.gitpodUrl"
:gitpod-enabled="mr.gitpodEnabled"
:gitpod-text="$options.i18n.gitpodText"
class="gl-display-none gl-md-display-inline-block gl-mr-3"
data-placement="bottom"
tabindex="0"
data-qa-selector="open_in_web_ide_button"
/>
<gl-button
v-gl-modal-directive="'modal-merge-info'"
:disabled="mr.sourceBranchRemoved"
......
......@@ -19,6 +19,7 @@ export default class MergeRequestStore {
this.setPaths(data);
this.setData(data);
this.setGitpodData(data);
}
setData(data, isRebased) {
......@@ -199,6 +200,12 @@ export default class MergeRequestStore {
}
}
setGitpodData(data) {
this.showGitpodButton = data.show_gitpod_button;
this.gitpodUrl = data.gitpod_url;
this.gitpodEnabled = data.gitpod_enabled;
}
setState() {
if (this.mergeOngoing) {
this.state = 'merging';
......
......@@ -59,11 +59,21 @@ export default {
required: false,
default: '',
},
webIdeText: {
type: String,
required: false,
default: '',
},
gitpodUrl: {
type: String,
required: false,
default: '',
},
gitpodText: {
type: String,
required: false,
default: '',
},
},
data() {
return {
......@@ -99,6 +109,17 @@ export default {
...handleOptions,
};
},
webIdeActionText() {
if (this.webIdeText) {
return this.webIdeText;
} else if (this.isBlob) {
return __('Edit in Web IDE');
} else if (this.isFork) {
return __('Edit fork in Web IDE');
}
return __('Web IDE');
},
webIdeAction() {
if (!this.showWebIdeButton) {
return null;
......@@ -111,17 +132,9 @@ export default {
}
: { href: this.webIdeUrl };
let text = __('Web IDE');
if (this.isBlob) {
text = __('Edit in Web IDE');
} else if (this.isFork) {
text = __('Edit fork in Web IDE');
}
return {
key: KEY_WEB_IDE,
text,
text: this.webIdeActionText,
secondaryText: __('Quickly and easily edit multiple files in your project.'),
tooltip: '',
attrs: {
......@@ -132,6 +145,9 @@ export default {
...handleOptions,
};
},
gitpodActionText() {
return this.gitpodText || __('Gitpod');
},
gitpodAction() {
if (!this.showGitpodButton) {
return null;
......@@ -145,7 +161,7 @@ export default {
return {
key: KEY_GITPOD,
text: __('Gitpod'),
text: this.gitpodActionText,
secondaryText,
tooltip: secondaryText,
attrs: {
......
......@@ -137,6 +137,23 @@ class MergeRequestWidgetEntity < Grape::Entity
merge_request.enabled_reports
end
expose :show_gitpod_button do |merge_request|
Gitlab::CurrentSettings.gitpod_enabled
end
expose :gitpod_url do |merge_request|
next unless Gitlab::CurrentSettings.gitpod_enabled
gitpod_url = Gitlab::CurrentSettings.gitpod_url
context_url = project_merge_request_url(merge_request.project, merge_request)
"#{gitpod_url}##{context_url}"
end
expose :gitpod_enabled do |merge_request|
current_user&.gitpod_enabled || false
end
private
delegate :current_user, to: :request
......
......@@ -97,4 +97,5 @@
#js-review-bar
= render 'projects/invite_members_modal', project: @project
- if Gitlab::CurrentSettings.gitpod_enabled && !current_user&.gitpod_enabled
= render 'shared/gitpod/enable_gitpod_modal'
......@@ -39748,6 +39748,9 @@ msgstr ""
msgid "mrWidget|More information"
msgstr ""
msgid "mrWidget|Open in Gitpod"
msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr ""
......@@ -39853,9 +39856,6 @@ msgstr ""
msgid "mrWidget|Use %{linkStart}CI pipelines to test your code%{linkEnd} by simply adding a GitLab CI configuration file to your project. It only takes a minute to make your code more secure and robust."
msgstr ""
msgid "mrWidget|You are not allowed to edit this project directly. Please fork to make changes."
msgstr ""
msgid "mrWidget|You can merge after removing denied licenses"
msgstr ""
......
import { shallowMount, mount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Header from '~/vue_merge_request_widget/components/mr_widget_header.vue';
import WebIdeLink from '~/vue_shared/components/web_ide_link.vue';
describe('MRWidgetHeader', () => {
let wrapper;
......@@ -35,6 +36,8 @@ describe('MRWidgetHeader', () => {
statusPath: 'abc',
};
const findWebIdeButton = () => wrapper.findComponent(WebIdeLink);
describe('computed', () => {
describe('shouldShowCommitsBehindText', () => {
it('return true when there are divergedCommitsCount', () => {
......@@ -147,73 +150,81 @@ describe('MRWidgetHeader', () => {
statusPath: 'abc',
sourceProjectFullPath: 'root/gitlab-ce',
targetProjectFullPath: 'gitlab-org/gitlab-ce',
gitpodEnabled: true,
showGitpodButton: true,
gitpodUrl: 'http://gitpod.localhost',
};
beforeEach(() => {
it('renders checkout branch button with modal trigger', () => {
createComponent({
mr: { ...mrDefaultOptions },
});
});
it('renders checkout branch button with modal trigger', () => {
const button = wrapper.find('.js-check-out-branch');
expect(button.text().trim()).toBe('Check out branch');
});
it('renders web ide button', async () => {
const button = wrapper.find('.js-web-ide');
await nextTick();
expect(button.text().trim()).toBe('Open in Web IDE');
expect(button.classes('disabled')).toBe(false);
expect(button.attributes('href')).toBe(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce',
);
});
it('renders web ide button in disabled state with no href', async () => {
const mr = { ...mrDefaultOptions, canPushToSourceBranch: false };
createComponent({ mr });
await nextTick();
const link = wrapper.find('.js-web-ide');
expect(link.attributes('disabled')).toBe('true');
expect(link.attributes('href')).toBeUndefined();
});
it('renders web ide button with blank query string if target & source project branch', async () => {
createComponent({ mr: { ...mrDefaultOptions, targetProjectFullPath: 'root/gitlab-ce' } });
it.each([
[
'renders web ide button',
{
mrProps: {},
relativeUrl: '',
webIdeUrl:
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce',
},
],
[
'renders web ide button with blank target_project, when mr has same target project',
{
mrProps: { targetProjectFullPath: 'root/gitlab-ce' },
relativeUrl: '',
webIdeUrl: '/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=',
},
],
[
'renders web ide button with relative url',
{
mrProps: { iid: 2 },
relativeUrl: '/gitlab',
webIdeUrl:
'/gitlab/-/ide/project/root/gitlab-ce/merge_requests/2?target_project=gitlab-org%2Fgitlab-ce',
},
],
])('%s', async (_, { mrProps, relativeUrl, webIdeUrl }) => {
gon.relative_url_root = relativeUrl;
createComponent({
mr: { ...mrDefaultOptions, ...mrProps },
});
await nextTick();
const button = wrapper.find('.js-web-ide');
expect(button.text().trim()).toBe('Open in Web IDE');
expect(button.attributes('href')).toBe(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=',
);
expect(findWebIdeButton().props()).toMatchObject({
showEditButton: false,
showWebIdeButton: true,
webIdeText: 'Open in Web IDE',
gitpodText: 'Open in Gitpod',
gitpodEnabled: true,
showGitpodButton: true,
gitpodUrl: 'http://gitpod.localhost',
webIdeUrl,
});
});
it('renders web ide button with relative URL', async () => {
gon.relative_url_root = '/gitlab';
createComponent({ mr: { ...mrDefaultOptions, iid: 2 } });
it('does not render web ide button if source branch is removed', async () => {
createComponent({ mr: { ...mrDefaultOptions, sourceBranchRemoved: true } });
await nextTick();
const button = wrapper.find('.js-web-ide');
expect(button.text().trim()).toBe('Open in Web IDE');
expect(button.attributes('href')).toBe(
'/gitlab/-/ide/project/root/gitlab-ce/merge_requests/2?target_project=gitlab-org%2Fgitlab-ce',
);
expect(findWebIdeButton().exists()).toBe(false);
});
it('renders download dropdown with links', () => {
createComponent({
mr: { ...mrDefaultOptions },
});
expectDownloadDropdownItems();
});
});
......
......@@ -281,6 +281,9 @@ export default {
security_reports_docs_path: 'security-reports-docs-path',
sast_comparison_path: '/sast_comparison_path',
secret_scanning_comparison_path: '/secret_scanning_comparison_path',
gitpod_enabled: true,
show_gitpod_button: true,
gitpod_url: 'http://gitpod.localhost',
};
export const mockStore = {
......
......@@ -10,6 +10,14 @@ describe('MergeRequestStore', () => {
store = new MergeRequestStore(mockData);
});
it('should initialize gitpod attributes', () => {
expect(store).toMatchObject({
gitpodEnabled: mockData.gitpod_enabled,
showGitpodButton: mockData.show_gitpod_button,
gitpodUrl: mockData.gitpod_url,
});
});
describe('setData', () => {
it('should set isSHAMismatch when the diff SHA changes', () => {
store.setData({ ...mockData, diff_head_sha: 'a-different-string' });
......
......@@ -84,6 +84,10 @@ describe('Web IDE link component', () => {
props: {},
expectedActions: [ACTION_WEB_IDE, ACTION_EDIT],
},
{
props: { webIdeText: 'Test Web IDE' },
expectedActions: [{ ...ACTION_WEB_IDE_EDIT_FORK, text: 'Test Web IDE' }, ACTION_EDIT],
},
{
props: { isFork: true },
expectedActions: [ACTION_WEB_IDE_EDIT_FORK, ACTION_EDIT],
......@@ -104,6 +108,10 @@ describe('Web IDE link component', () => {
props: { showGitpodButton: true, gitpodEnabled: false },
expectedActions: [ACTION_WEB_IDE, ACTION_EDIT, ACTION_GITPOD_ENABLE],
},
{
props: { showEditButton: false, showGitpodButton: true, gitpodText: 'Test Gitpod' },
expectedActions: [ACTION_WEB_IDE, { ...ACTION_GITPOD_ENABLE, text: 'Test Gitpod' }],
},
{
props: { showEditButton: false },
expectedActions: [ACTION_WEB_IDE],
......
......@@ -354,4 +354,45 @@ RSpec.describe MergeRequestWidgetEntity do
end
end
end
describe 'when gitpod is disabled' do
before do
allow(Gitlab::CurrentSettings).to receive(:gitpod_enabled).and_return(false)
end
it 'exposes gitpod attributes' do
expect(subject).to include(
show_gitpod_button: false,
gitpod_url: nil,
gitpod_enabled: false
)
end
end
describe 'when gitpod is enabled' do
before do
allow(Gitlab::CurrentSettings).to receive(:gitpod_enabled).and_return(true)
allow(Gitlab::CurrentSettings).to receive(:gitpod_url).and_return("https://gitpod.example.com")
end
it 'exposes gitpod attributes' do
mr_url = Gitlab::Routing.url_helpers.project_merge_request_url(resource.project, resource)
expect(subject).to include(
show_gitpod_button: true,
gitpod_url: "https://gitpod.example.com##{mr_url}",
gitpod_enabled: false
)
end
describe 'when gitpod is enabled for user' do
before do
allow(user).to receive(:gitpod_enabled).and_return(true)
end
it 'exposes gitpod_enabled as true' do
expect(subject[:gitpod_enabled]).to be(true)
end
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe 'projects/merge_requests/show.html.haml', :aggregate_failures do
using RSpec::Parameterized::TableSyntax
include_context 'merge request show action'
before do
......@@ -43,4 +45,32 @@ RSpec.describe 'projects/merge_requests/show.html.haml', :aggregate_failures do
end
end
end
describe 'gitpod modal' do
let(:gitpod_modal_selector) { '#modal-enable-gitpod' }
let(:user) { create(:user) }
let(:user_gitpod_enabled) { create(:user).tap { |x| x.update!(gitpod_enabled: true) } }
where(:site_enabled, :current_user, :should_show) do
false | ref(:user) | false
true | ref(:user) | true
true | nil | true
true | ref(:user_gitpod_enabled) | false
end
with_them do
it 'handles rendering gitpod user enable modal' do
allow(Gitlab::CurrentSettings).to receive(:gitpod_enabled).and_return(site_enabled)
allow(view).to receive(:current_user).and_return(current_user)
render
if should_show
expect(rendered).to have_css(gitpod_modal_selector)
else
expect(rendered).to have_no_css(gitpod_modal_selector)
end
end
end
end
end
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