Skip to content
Snippets Groups Projects
Commit f8d8b80c authored by Cornelius Ludmann's avatar Cornelius Ludmann Committed by Kerri Miller
Browse files

Add Gitpod button to MR page

parent b011b9db
No related branches found
No related tags found
No related merge requests found
Showing
with 230 additions and 94 deletions
Loading
Loading
@@ -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';
 
Loading
Loading
@@ -30,6 +31,7 @@ export default {
GlDropdownItem,
GlLink,
GlSprintf,
WebIdeLink,
},
directives: {
GlTooltip: GlTooltipDirective,
Loading
Loading
@@ -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>
Loading
Loading
@@ -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"
Loading
Loading
Loading
Loading
@@ -19,6 +19,7 @@ export default class MergeRequestStore {
this.setPaths(data);
 
this.setData(data);
this.setGitpodData(data);
}
 
setData(data, isRebased) {
Loading
Loading
@@ -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';
Loading
Loading
Loading
Loading
@@ -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 {
Loading
Loading
@@ -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;
Loading
Loading
@@ -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: {
Loading
Loading
@@ -132,6 +145,9 @@ export default {
...handleOptions,
};
},
gitpodActionText() {
return this.gitpodText || __('Gitpod');
},
gitpodAction() {
if (!this.showGitpodButton) {
return null;
Loading
Loading
@@ -145,7 +161,7 @@ export default {
 
return {
key: KEY_GITPOD,
text: __('Gitpod'),
text: this.gitpodActionText,
secondaryText,
tooltip: secondaryText,
attrs: {
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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'
Loading
Loading
@@ -39748,6 +39748,9 @@ msgstr ""
msgid "mrWidget|More information"
msgstr ""
 
msgid "mrWidget|Open in Gitpod"
msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr ""
 
Loading
Loading
@@ -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 ""
 
Loading
Loading
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;
Loading
Loading
@@ -35,6 +36,8 @@ describe('MRWidgetHeader', () => {
statusPath: 'abc',
};
 
const findWebIdeButton = () => wrapper.findComponent(WebIdeLink);
describe('computed', () => {
describe('shouldShowCommitsBehindText', () => {
it('return true when there are divergedCommitsCount', () => {
Loading
Loading
@@ -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();
});
});
Loading
Loading
Loading
Loading
@@ -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 = {
Loading
Loading
Loading
Loading
@@ -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' });
Loading
Loading
Loading
Loading
@@ -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],
Loading
Loading
@@ -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],
Loading
Loading
Loading
Loading
@@ -354,4 +354,45 @@
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
Loading
Loading
@@ -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
Loading
Loading
@@ -43,4 +45,32 @@
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
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