Commit 572b5260 authored by Kerri Miller's avatar Kerri Miller

Merge branch 'gitpod-button-on-mr-page' into 'master'

Add Gitpod button to MR page

See merge request gitlab-org/gitlab!43352
parents 396bc72d f8d8b80c
...@@ -14,6 +14,7 @@ import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility'; ...@@ -14,6 +14,7 @@ import { mergeUrlParams, webIDEUrl } from '~/lib/utils/url_utility';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import clipboardButton from '~/vue_shared/components/clipboard_button.vue'; import clipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.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 MrWidgetHowToMergeModal from './mr_widget_how_to_merge_modal.vue';
import MrWidgetIcon from './mr_widget_icon.vue'; import MrWidgetIcon from './mr_widget_icon.vue';
...@@ -30,6 +31,7 @@ export default { ...@@ -30,6 +31,7 @@ export default {
GlDropdownItem, GlDropdownItem,
GlLink, GlLink,
GlSprintf, GlSprintf,
WebIdeLink,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -56,7 +58,6 @@ export default { ...@@ -56,7 +58,6 @@ export default {
}); });
}, },
webIdePath() { webIdePath() {
if (this.mr.canPushToSourceBranch) {
return mergeUrlParams( return mergeUrlParams(
{ {
target_project: target_project:
...@@ -66,21 +67,15 @@ export default { ...@@ -66,21 +67,15 @@ export default {
}, },
webIDEUrl(`/${this.mr.sourceProjectFullPath}/merge_requests/${this.mr.iid}`), 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.',
)
: '';
}, },
isFork() { isFork() {
return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath; return this.mr.sourceProjectFullPath !== this.mr.targetProjectFullPath;
}, },
}, },
i18n: {
webIdeText: s__('mrWidget|Open in Web IDE'),
gitpodText: s__('mrWidget|Open in Gitpod'),
},
}; };
</script> </script>
<template> <template>
...@@ -123,22 +118,21 @@ export default { ...@@ -123,22 +118,21 @@ export default {
<div class="branch-actions d-flex"> <div class="branch-actions d-flex">
<template v-if="mr.isOpen"> <template v-if="mr.isOpen">
<span <web-ide-link
v-if="!mr.sourceBranchRemoved" v-if="!mr.sourceBranchRemoved"
v-gl-tooltip :show-edit-button="false"
:title="ideButtonTitle" :show-web-ide-button="true"
class="gl-display-none d-md-inline-block gl-mr-3" :web-ide-url="webIdePath"
:tabindex="ideButtonTitle ? 0 : null" :web-ide-text="$options.i18n.webIdeText"
> :show-gitpod-button="mr.showGitpodButton"
<gl-button :gitpod-url="mr.gitpodUrl"
:href="webIdePath" :gitpod-enabled="mr.gitpodEnabled"
:disabled="!mr.canPushToSourceBranch" :gitpod-text="$options.i18n.gitpodText"
class="js-web-ide" 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" data-qa-selector="open_in_web_ide_button"
> />
{{ s__('mrWidget|Open in Web IDE') }}
</gl-button>
</span>
<gl-button <gl-button
v-gl-modal-directive="'modal-merge-info'" v-gl-modal-directive="'modal-merge-info'"
:disabled="mr.sourceBranchRemoved" :disabled="mr.sourceBranchRemoved"
......
...@@ -19,6 +19,7 @@ export default class MergeRequestStore { ...@@ -19,6 +19,7 @@ export default class MergeRequestStore {
this.setPaths(data); this.setPaths(data);
this.setData(data); this.setData(data);
this.setGitpodData(data);
} }
setData(data, isRebased) { setData(data, isRebased) {
...@@ -199,6 +200,12 @@ export default class MergeRequestStore { ...@@ -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() { setState() {
if (this.mergeOngoing) { if (this.mergeOngoing) {
this.state = 'merging'; this.state = 'merging';
......
...@@ -59,11 +59,21 @@ export default { ...@@ -59,11 +59,21 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
webIdeText: {
type: String,
required: false,
default: '',
},
gitpodUrl: { gitpodUrl: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
gitpodText: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
...@@ -99,6 +109,17 @@ export default { ...@@ -99,6 +109,17 @@ export default {
...handleOptions, ...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() { webIdeAction() {
if (!this.showWebIdeButton) { if (!this.showWebIdeButton) {
return null; return null;
...@@ -111,17 +132,9 @@ export default { ...@@ -111,17 +132,9 @@ export default {
} }
: { href: this.webIdeUrl }; : { 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 { return {
key: KEY_WEB_IDE, key: KEY_WEB_IDE,
text, text: this.webIdeActionText,
secondaryText: __('Quickly and easily edit multiple files in your project.'), secondaryText: __('Quickly and easily edit multiple files in your project.'),
tooltip: '', tooltip: '',
attrs: { attrs: {
...@@ -132,6 +145,9 @@ export default { ...@@ -132,6 +145,9 @@ export default {
...handleOptions, ...handleOptions,
}; };
}, },
gitpodActionText() {
return this.gitpodText || __('Gitpod');
},
gitpodAction() { gitpodAction() {
if (!this.showGitpodButton) { if (!this.showGitpodButton) {
return null; return null;
...@@ -145,7 +161,7 @@ export default { ...@@ -145,7 +161,7 @@ export default {
return { return {
key: KEY_GITPOD, key: KEY_GITPOD,
text: __('Gitpod'), text: this.gitpodActionText,
secondaryText, secondaryText,
tooltip: secondaryText, tooltip: secondaryText,
attrs: { attrs: {
......
...@@ -137,6 +137,23 @@ class MergeRequestWidgetEntity < Grape::Entity ...@@ -137,6 +137,23 @@ class MergeRequestWidgetEntity < Grape::Entity
merge_request.enabled_reports merge_request.enabled_reports
end 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 private
delegate :current_user, to: :request delegate :current_user, to: :request
......
...@@ -97,4 +97,5 @@ ...@@ -97,4 +97,5 @@
#js-review-bar #js-review-bar
= render 'projects/invite_members_modal', project: @project = 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 "" ...@@ -39748,6 +39748,9 @@ msgstr ""
msgid "mrWidget|More information" msgid "mrWidget|More information"
msgstr "" msgstr ""
msgid "mrWidget|Open in Gitpod"
msgstr ""
msgid "mrWidget|Open in Web IDE" msgid "mrWidget|Open in Web IDE"
msgstr "" msgstr ""
...@@ -39853,9 +39856,6 @@ 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." 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 "" 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" msgid "mrWidget|You can merge after removing denied licenses"
msgstr "" msgstr ""
......
import { shallowMount, mount } from '@vue/test-utils'; import { shallowMount, mount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import Header from '~/vue_merge_request_widget/components/mr_widget_header.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', () => { describe('MRWidgetHeader', () => {
let wrapper; let wrapper;
...@@ -35,6 +36,8 @@ describe('MRWidgetHeader', () => { ...@@ -35,6 +36,8 @@ describe('MRWidgetHeader', () => {
statusPath: 'abc', statusPath: 'abc',
}; };
const findWebIdeButton = () => wrapper.findComponent(WebIdeLink);
describe('computed', () => { describe('computed', () => {
describe('shouldShowCommitsBehindText', () => { describe('shouldShowCommitsBehindText', () => {
it('return true when there are divergedCommitsCount', () => { it('return true when there are divergedCommitsCount', () => {
...@@ -147,73 +150,81 @@ describe('MRWidgetHeader', () => { ...@@ -147,73 +150,81 @@ describe('MRWidgetHeader', () => {
statusPath: 'abc', statusPath: 'abc',
sourceProjectFullPath: 'root/gitlab-ce', sourceProjectFullPath: 'root/gitlab-ce',
targetProjectFullPath: 'gitlab-org/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({ createComponent({
mr: { ...mrDefaultOptions }, mr: { ...mrDefaultOptions },
}); });
});
it('renders checkout branch button with modal trigger', () => {
const button = wrapper.find('.js-check-out-branch'); const button = wrapper.find('.js-check-out-branch');
expect(button.text().trim()).toBe('Check out branch'); expect(button.text().trim()).toBe('Check out branch');
}); });
it('renders web ide button', async () => { it.each([
const button = wrapper.find('.js-web-ide'); [
'renders web ide button',
await nextTick(); {
mrProps: {},
expect(button.text().trim()).toBe('Open in Web IDE'); relativeUrl: '',
expect(button.classes('disabled')).toBe(false); webIdeUrl:
expect(button.attributes('href')).toBe(
'/-/ide/project/root/gitlab-ce/merge_requests/1?target_project=gitlab-org%2Fgitlab-ce', '/-/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 },
}); });
it('renders web ide button in disabled state with no href', async () => {
const mr = { ...mrDefaultOptions, canPushToSourceBranch: false };
createComponent({ mr });
await nextTick(); await nextTick();
const link = wrapper.find('.js-web-ide'); expect(findWebIdeButton().props()).toMatchObject({
showEditButton: false,
expect(link.attributes('disabled')).toBe('true'); showWebIdeButton: true,
expect(link.attributes('href')).toBeUndefined(); webIdeText: 'Open in Web IDE',
gitpodText: 'Open in Gitpod',
gitpodEnabled: true,
showGitpodButton: true,
gitpodUrl: 'http://gitpod.localhost',
webIdeUrl,
}); });
it('renders web ide button with blank query string if target & source project branch', async () => {
createComponent({ mr: { ...mrDefaultOptions, targetProjectFullPath: 'root/gitlab-ce' } });
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=',
);
}); });
it('renders web ide button with relative URL', async () => { it('does not render web ide button if source branch is removed', async () => {
gon.relative_url_root = '/gitlab'; createComponent({ mr: { ...mrDefaultOptions, sourceBranchRemoved: true } });
createComponent({ mr: { ...mrDefaultOptions, iid: 2 } });
await nextTick(); await nextTick();
const button = wrapper.find('.js-web-ide'); expect(findWebIdeButton().exists()).toBe(false);
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',
);
}); });
it('renders download dropdown with links', () => { it('renders download dropdown with links', () => {
createComponent({
mr: { ...mrDefaultOptions },
});
expectDownloadDropdownItems(); expectDownloadDropdownItems();
}); });
}); });
......
...@@ -281,6 +281,9 @@ export default { ...@@ -281,6 +281,9 @@ export default {
security_reports_docs_path: 'security-reports-docs-path', security_reports_docs_path: 'security-reports-docs-path',
sast_comparison_path: '/sast_comparison_path', sast_comparison_path: '/sast_comparison_path',
secret_scanning_comparison_path: '/secret_scanning_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 = { export const mockStore = {
......
...@@ -10,6 +10,14 @@ describe('MergeRequestStore', () => { ...@@ -10,6 +10,14 @@ describe('MergeRequestStore', () => {
store = new MergeRequestStore(mockData); 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', () => { describe('setData', () => {
it('should set isSHAMismatch when the diff SHA changes', () => { it('should set isSHAMismatch when the diff SHA changes', () => {
store.setData({ ...mockData, diff_head_sha: 'a-different-string' }); store.setData({ ...mockData, diff_head_sha: 'a-different-string' });
......
...@@ -84,6 +84,10 @@ describe('Web IDE link component', () => { ...@@ -84,6 +84,10 @@ describe('Web IDE link component', () => {
props: {}, props: {},
expectedActions: [ACTION_WEB_IDE, ACTION_EDIT], 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 }, props: { isFork: true },
expectedActions: [ACTION_WEB_IDE_EDIT_FORK, ACTION_EDIT], expectedActions: [ACTION_WEB_IDE_EDIT_FORK, ACTION_EDIT],
...@@ -104,6 +108,10 @@ describe('Web IDE link component', () => { ...@@ -104,6 +108,10 @@ describe('Web IDE link component', () => {
props: { showGitpodButton: true, gitpodEnabled: false }, props: { showGitpodButton: true, gitpodEnabled: false },
expectedActions: [ACTION_WEB_IDE, ACTION_EDIT, ACTION_GITPOD_ENABLE], 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 }, props: { showEditButton: false },
expectedActions: [ACTION_WEB_IDE], expectedActions: [ACTION_WEB_IDE],
......
...@@ -354,4 +354,45 @@ RSpec.describe MergeRequestWidgetEntity do ...@@ -354,4 +354,45 @@ RSpec.describe MergeRequestWidgetEntity do
end end
end 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 end
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'projects/merge_requests/show.html.haml', :aggregate_failures do RSpec.describe 'projects/merge_requests/show.html.haml', :aggregate_failures do
using RSpec::Parameterized::TableSyntax
include_context 'merge request show action' include_context 'merge request show action'
before do before do
...@@ -43,4 +45,32 @@ RSpec.describe 'projects/merge_requests/show.html.haml', :aggregate_failures do ...@@ -43,4 +45,32 @@ RSpec.describe 'projects/merge_requests/show.html.haml', :aggregate_failures do
end end
end 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 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