Commit e66a0e30 authored by Paul Slaughter's avatar Paul Slaughter Committed by Alex Kalderimis

WebIDE show fork button when cannot push code

- Uses the same BE helpers that are used in the
project homepage.
- Uses a ViewModel approach, such that a getter
returns an easily consumable model so that the
cannot_push_code_alert component is trivial.
parent d3635ba2
<script>
import { GlAlert, GlButton } from '@gitlab/ui';
export default {
components: {
GlAlert,
GlButton,
},
props: {
message: {
type: String,
required: true,
},
action: {
type: Object,
required: false,
default: null,
},
},
computed: {
hasAction() {
return Boolean(this.action?.href);
},
actionButtonMethod() {
return this.action?.isForm ? 'post' : null;
},
},
};
</script>
<template>
<gl-alert :dismissible="false">
{{ message }}
<template v-if="hasAction" #actions>
<gl-button variant="confirm" :href="action.href" :data-method="actionButtonMethod">
{{ action.text }}
</gl-button>
</template>
</gl-alert>
</template>
<script> <script>
import { GlAlert, GlButton, GlLoadingIcon } from '@gitlab/ui'; import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { import {
...@@ -14,6 +14,7 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; ...@@ -14,6 +14,7 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { modalTypes } from '../constants'; import { modalTypes } from '../constants';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import { measurePerformance } from '../utils'; import { measurePerformance } from '../utils';
import CannotPushCodeAlert from './cannot_push_code_alert.vue';
import IdeSidebar from './ide_side_bar.vue'; import IdeSidebar from './ide_side_bar.vue';
import RepoEditor from './repo_editor.vue'; import RepoEditor from './repo_editor.vue';
...@@ -29,7 +30,6 @@ export default { ...@@ -29,7 +30,6 @@ export default {
components: { components: {
IdeSidebar, IdeSidebar,
RepoEditor, RepoEditor,
GlAlert,
GlButton, GlButton,
GlLoadingIcon, GlLoadingIcon,
ErrorMessage: () => import(/* webpackChunkName: 'ide_runtime' */ './error_message.vue'), ErrorMessage: () => import(/* webpackChunkName: 'ide_runtime' */ './error_message.vue'),
...@@ -41,6 +41,7 @@ export default { ...@@ -41,6 +41,7 @@ export default {
import(/* webpackChunkName: 'ide_runtime' */ '~/vue_shared/components/file_finder/index.vue'), import(/* webpackChunkName: 'ide_runtime' */ '~/vue_shared/components/file_finder/index.vue'),
RightPane: () => import(/* webpackChunkName: 'ide_runtime' */ './panes/right.vue'), RightPane: () => import(/* webpackChunkName: 'ide_runtime' */ './panes/right.vue'),
NewModal: () => import(/* webpackChunkName: 'ide_runtime' */ './new_dropdown/modal.vue'), NewModal: () => import(/* webpackChunkName: 'ide_runtime' */ './new_dropdown/modal.vue'),
CannotPushCodeAlert,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
data() { data() {
...@@ -120,9 +121,11 @@ export default { ...@@ -120,9 +121,11 @@ export default {
class="ide position-relative d-flex flex-column align-items-stretch" class="ide position-relative d-flex flex-column align-items-stretch"
:class="{ [`theme-${themeName}`]: themeName }" :class="{ [`theme-${themeName}`]: themeName }"
> >
<gl-alert v-if="!canPushCodeStatus.isAllowed" :dismissible="false">{{ <cannot-push-code-alert
canPushCodeStatus.message v-if="!canPushCodeStatus.isAllowed"
}}</gl-alert> :message="canPushCodeStatus.message"
:action="canPushCodeStatus.action"
/>
<error-message v-if="errorMessage" :message="errorMessage" /> <error-message v-if="errorMessage" :message="errorMessage" />
<div class="ide-view flex-grow d-flex"> <div class="ide-view flex-grow d-flex">
<template v-if="loadDeferred"> <template v-if="loadDeferred">
......
...@@ -54,6 +54,7 @@ export function initIde(el, options = {}) { ...@@ -54,6 +54,7 @@ export function initIde(el, options = {}) {
}); });
this.setLinks({ this.setLinks({
webIDEHelpPagePath: el.dataset.webIdeHelpPagePath, webIDEHelpPagePath: el.dataset.webIdeHelpPagePath,
forkInfo: el.dataset.forkInfo ? JSON.parse(el.dataset.forkInfo) : null,
}); });
this.setInitialData({ this.setInitialData({
clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled), clientsidePreviewEnabled: parseBoolean(el.dataset.clientsidePreviewEnabled),
......
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export const MSG_CANNOT_PUSH_CODE = s__( export const MSG_CANNOT_PUSH_CODE_SHOULD_FORK = s__(
'WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request.', 'WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request.',
); );
export const MSG_CANNOT_PUSH_CODE_SHORT = s__( export const MSG_CANNOT_PUSH_CODE_GO_TO_FORK = s__(
'WebIDE|You need permission to edit files directly in this project. Go to your fork to make changes and submit a merge request.',
);
export const MSG_CANNOT_PUSH_CODE = s__(
'WebIDE|You need permission to edit files directly in this project.', 'WebIDE|You need permission to edit files directly in this project.',
); );
...@@ -15,3 +19,7 @@ export const MSG_CANNOT_PUSH_UNSIGNED = s__( ...@@ -15,3 +19,7 @@ export const MSG_CANNOT_PUSH_UNSIGNED = s__(
export const MSG_CANNOT_PUSH_UNSIGNED_SHORT = s__( export const MSG_CANNOT_PUSH_UNSIGNED_SHORT = s__(
'WebIDE|This project does not accept unsigned commits.', 'WebIDE|This project does not accept unsigned commits.',
); );
export const MSG_FORK = s__('WebIDE|Fork project');
export const MSG_GO_TO_FORK = s__('WebIDE|Go to fork');
...@@ -11,12 +11,42 @@ import { ...@@ -11,12 +11,42 @@ import {
} from '../constants'; } from '../constants';
import { import {
MSG_CANNOT_PUSH_CODE, MSG_CANNOT_PUSH_CODE,
MSG_CANNOT_PUSH_CODE_SHORT, MSG_CANNOT_PUSH_CODE_SHOULD_FORK,
MSG_CANNOT_PUSH_CODE_GO_TO_FORK,
MSG_CANNOT_PUSH_UNSIGNED, MSG_CANNOT_PUSH_UNSIGNED,
MSG_CANNOT_PUSH_UNSIGNED_SHORT, MSG_CANNOT_PUSH_UNSIGNED_SHORT,
MSG_FORK,
MSG_GO_TO_FORK,
} from '../messages'; } from '../messages';
import { getChangesCountForFiles, filePathMatches } from './utils'; import { getChangesCountForFiles, filePathMatches } from './utils';
const getCannotPushCodeViewModel = (state) => {
const { ide_path: idePath, fork_path: forkPath } = state.links.forkInfo || {};
if (idePath) {
return {
message: MSG_CANNOT_PUSH_CODE_GO_TO_FORK,
action: {
href: idePath,
text: MSG_GO_TO_FORK,
},
};
} else if (forkPath) {
return {
message: MSG_CANNOT_PUSH_CODE_SHOULD_FORK,
action: {
href: forkPath,
isForm: true,
text: MSG_FORK,
},
};
}
return {
message: MSG_CANNOT_PUSH_CODE,
};
};
export const activeFile = (state) => state.openFiles.find((file) => file.active) || null; export const activeFile = (state) => state.openFiles.find((file) => file.active) || null;
export const addedFiles = (state) => state.changedFiles.filter((f) => f.tempFile); export const addedFiles = (state) => state.changedFiles.filter((f) => f.tempFile);
...@@ -188,8 +218,8 @@ export const canPushCodeStatus = (state, getters) => { ...@@ -188,8 +218,8 @@ export const canPushCodeStatus = (state, getters) => {
if (!canPushCode) { if (!canPushCode) {
return { return {
isAllowed: false, isAllowed: false,
message: MSG_CANNOT_PUSH_CODE, messageShort: MSG_CANNOT_PUSH_CODE,
messageShort: MSG_CANNOT_PUSH_CODE_SHORT, ...getCannotPushCodeViewModel(state),
}; };
} }
......
...@@ -27,9 +27,20 @@ class IdeController < ApplicationController ...@@ -27,9 +27,20 @@ class IdeController < ApplicationController
@branch = params[:branch] @branch = params[:branch]
@path = params[:path] @path = params[:path]
@merge_request = params[:merge_request_id] @merge_request = params[:merge_request_id]
@fork_info = fork_info(project, @branch)
end
def fork_info(project, branch)
return if can?(current_user, :push_code, project)
existing_fork = current_user.fork_of(project)
unless can?(current_user, :push_code, project) if existing_fork
@forked_project = ForkProjectsFinder.new(project, current_user: current_user).execute.first path = helpers.ide_edit_path(existing_fork, branch, '')
{ ide_path: path }
elsif can?(current_user, :fork_project, project)
path = helpers.ide_fork_and_edit_path(project, branch, '', with_notice: false)
{ fork_path: path }
end end
end end
......
...@@ -41,20 +41,20 @@ module BlobHelper ...@@ -41,20 +41,20 @@ module BlobHelper
result result
end end
def ide_fork_and_edit_path(project = @project, ref = @ref, path = @path, options = {}) def ide_fork_and_edit_path(project = @project, ref = @ref, path = @path, with_notice: true)
fork_path_for_current_user(project, ide_edit_path(project, ref, path)) fork_path_for_current_user(project, ide_edit_path(project, ref, path), with_notice: with_notice)
end end
def fork_and_edit_path(project = @project, ref = @ref, path = @path, options = {}) def fork_and_edit_path(project = @project, ref = @ref, path = @path, options = {})
fork_path_for_current_user(project, edit_blob_path(project, ref, path, options)) fork_path_for_current_user(project, edit_blob_path(project, ref, path, options))
end end
def fork_path_for_current_user(project, path) def fork_path_for_current_user(project, path, with_notice: true)
return unless current_user return unless current_user
project_forks_path(project, project_forks_path(project,
namespace_key: current_user.namespace&.id, namespace_key: current_user.namespace&.id,
continue: edit_blob_fork_params(path)) continue: edit_blob_fork_params(path, with_notice: with_notice))
end end
def encode_ide_path(path) def encode_ide_path(path)
...@@ -330,12 +330,12 @@ module BlobHelper ...@@ -330,12 +330,12 @@ module BlobHelper
blob if blob&.readable_text? blob if blob&.readable_text?
end end
def edit_blob_fork_params(path) def edit_blob_fork_params(path, with_notice: true)
{ {
to: path, to: path,
notice: edit_in_new_fork_notice, notice: (edit_in_new_fork_notice if with_notice),
notice_now: edit_in_new_fork_notice_now notice_now: (edit_in_new_fork_notice_now if with_notice)
} }.compact
end end
def edit_modify_file_fork_params(action) def edit_modify_file_fork_params(action)
......
...@@ -16,7 +16,7 @@ module IdeHelper ...@@ -16,7 +16,7 @@ module IdeHelper
'branch-name' => @branch, 'branch-name' => @branch,
'file-path' => @path, 'file-path' => @path,
'merge-request' => @merge_request, 'merge-request' => @merge_request,
'forked-project' => convert_to_project_entity_json(@forked_project), 'fork-info' => @fork_info&.to_json,
'project' => convert_to_project_entity_json(@project) 'project' => convert_to_project_entity_json(@project)
} }
end end
......
---
title: WebIDE show fork button when cannot push code
merge_request: 56608
author:
type: changed
...@@ -33912,6 +33912,12 @@ msgstr "" ...@@ -33912,6 +33912,12 @@ msgstr ""
msgid "WebAuthn only works with HTTPS-enabled websites. Contact your administrator for more details." msgid "WebAuthn only works with HTTPS-enabled websites. Contact your administrator for more details."
msgstr "" msgstr ""
msgid "WebIDE|Fork project"
msgstr ""
msgid "WebIDE|Go to fork"
msgstr ""
msgid "WebIDE|Merge request" msgid "WebIDE|Merge request"
msgstr "" msgstr ""
...@@ -33927,6 +33933,9 @@ msgstr "" ...@@ -33927,6 +33933,9 @@ msgstr ""
msgid "WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request." msgid "WebIDE|You need permission to edit files directly in this project. Fork this project to make your changes and submit a merge request."
msgstr "" msgstr ""
msgid "WebIDE|You need permission to edit files directly in this project. Go to your fork to make changes and submit a merge request."
msgstr ""
msgid "Webhook" msgid "Webhook"
msgstr "" msgstr ""
......
import { GlButton, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
import CannotPushCodeAlert from '~/ide/components/cannot_push_code_alert.vue';
const TEST_MESSAGE = 'Hello test message!';
const TEST_HREF = '/test/path/to/fork';
const TEST_BUTTON_TEXT = 'Fork text';
describe('ide/components/cannot_push_code_alert', () => {
let wrapper;
afterEach(() => {
wrapper.destroy();
});
const createComponent = (props = {}) => {
wrapper = shallowMount(CannotPushCodeAlert, {
propsData: {
message: TEST_MESSAGE,
...props,
},
stubs: {
GlAlert: {
...stubComponent(GlAlert),
template: `<div><slot></slot><slot name="actions"></slot></div>`,
},
},
});
};
const findAlert = () => wrapper.findComponent(GlAlert);
const findButtonData = () => {
const button = findAlert().findComponent(GlButton);
if (!button.exists()) {
return null;
}
return {
href: button.attributes('href'),
method: button.attributes('data-method'),
text: button.text(),
};
};
describe('without actions', () => {
beforeEach(() => {
createComponent();
});
it('shows alert with message', () => {
expect(findAlert().props()).toMatchObject({ dismissible: false });
expect(findAlert().text()).toBe(TEST_MESSAGE);
});
});
describe.each`
action | buttonData
${{}} | ${null}
${{ href: TEST_HREF, text: TEST_BUTTON_TEXT }} | ${{ href: TEST_HREF, text: TEST_BUTTON_TEXT }}
${{ href: TEST_HREF, text: TEST_BUTTON_TEXT, isForm: true }} | ${{ href: TEST_HREF, text: TEST_BUTTON_TEXT, method: 'post' }}
`('with action=$action', ({ action, buttonData }) => {
beforeEach(() => {
createComponent({ action });
});
it(`show button=${JSON.stringify(buttonData)}`, () => {
expect(findButtonData()).toEqual(buttonData);
});
});
});
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
createBranchChangedCommitError, createBranchChangedCommitError,
branchAlreadyExistsCommitError, branchAlreadyExistsCommitError,
} from '~/ide/lib/errors'; } from '~/ide/lib/errors';
import { MSG_CANNOT_PUSH_CODE_SHORT } from '~/ide/messages'; import { MSG_CANNOT_PUSH_CODE } from '~/ide/messages';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import { COMMIT_TO_NEW_BRANCH } from '~/ide/stores/modules/commit/constants'; import { COMMIT_TO_NEW_BRANCH } from '~/ide/stores/modules/commit/constants';
...@@ -85,8 +85,8 @@ describe('IDE commit form', () => { ...@@ -85,8 +85,8 @@ describe('IDE commit form', () => {
${'when there are no changes'} | ${[]} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${''} ${'when there are no changes'} | ${[]} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${''}
${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${false} | ${''} ${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${false} | ${''}
${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToCommitView} | ${findCommitButtonData} | ${false} | ${''} ${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToCommitView} | ${findCommitButtonData} | ${false} | ${''}
${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${MSG_CANNOT_PUSH_CODE_SHORT} ${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${MSG_CANNOT_PUSH_CODE}
${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToCommitView} | ${findCommitButtonData} | ${true} | ${MSG_CANNOT_PUSH_CODE_SHORT} ${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToCommitView} | ${findCommitButtonData} | ${true} | ${MSG_CANNOT_PUSH_CODE}
`('$desc', ({ stagedFiles, userPermissions, viewFn, buttonFn, disabled, tooltip }) => { `('$desc', ({ stagedFiles, userPermissions, viewFn, buttonFn, disabled, tooltip }) => {
beforeEach(async () => { beforeEach(async () => {
store.state.stagedFiles = stagedFiles; store.state.stagedFiles = stagedFiles;
......
import { GlAlert } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import CannotPushCodeAlert from '~/ide/components/cannot_push_code_alert.vue';
import ErrorMessage from '~/ide/components/error_message.vue'; import ErrorMessage from '~/ide/components/error_message.vue';
import Ide from '~/ide/components/ide.vue'; import Ide from '~/ide/components/ide.vue';
import { MSG_CANNOT_PUSH_CODE } from '~/ide/messages'; import { MSG_CANNOT_PUSH_CODE_GO_TO_FORK, MSG_GO_TO_FORK } from '~/ide/messages';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import { file } from '../helpers'; import { file } from '../helpers';
import { projectData } from '../mock_data'; import { projectData } from '../mock_data';
...@@ -12,14 +12,15 @@ import { projectData } from '../mock_data'; ...@@ -12,14 +12,15 @@ import { projectData } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
const TEST_FORK_IDE_PATH = '/test/ide/path';
describe('WebIDE', () => { describe('WebIDE', () => {
const emptyProjData = { ...projectData, empty_repo: true, branches: {} }; const emptyProjData = { ...projectData, empty_repo: true, branches: {} };
let store;
let wrapper; let wrapper;
const createComponent = ({ projData = emptyProjData, state = {} } = {}) => { const createComponent = ({ projData = emptyProjData, state = {} } = {}) => {
const store = createStore();
store.state.currentProjectId = 'abcproject'; store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master'; store.state.currentBranchId = 'master';
store.state.projects.abcproject = projData && { ...projData }; store.state.projects.abcproject = projData && { ...projData };
...@@ -37,7 +38,11 @@ describe('WebIDE', () => { ...@@ -37,7 +38,11 @@ describe('WebIDE', () => {
}); });
}; };
const findAlert = () => wrapper.find(GlAlert); const findAlert = () => wrapper.findComponent(CannotPushCodeAlert);
beforeEach(() => {
store = createStore();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -148,6 +153,12 @@ describe('WebIDE', () => { ...@@ -148,6 +153,12 @@ describe('WebIDE', () => {
}); });
it('when user cannot push code, shows alert', () => { it('when user cannot push code, shows alert', () => {
store.state.links = {
forkInfo: {
ide_path: TEST_FORK_IDE_PATH,
},
};
createComponent({ createComponent({
projData: { projData: {
userPermissions: { userPermissions: {
...@@ -157,9 +168,12 @@ describe('WebIDE', () => { ...@@ -157,9 +168,12 @@ describe('WebIDE', () => {
}); });
expect(findAlert().props()).toMatchObject({ expect(findAlert().props()).toMatchObject({
dismissible: false, message: MSG_CANNOT_PUSH_CODE_GO_TO_FORK,
action: {
href: TEST_FORK_IDE_PATH,
text: MSG_GO_TO_FORK,
},
}); });
expect(findAlert().text()).toBe(MSG_CANNOT_PUSH_CODE);
}); });
it.each` it.each`
......
...@@ -6,15 +6,20 @@ import { ...@@ -6,15 +6,20 @@ import {
} from '~/ide/constants'; } from '~/ide/constants';
import { import {
MSG_CANNOT_PUSH_CODE, MSG_CANNOT_PUSH_CODE,
MSG_CANNOT_PUSH_CODE_SHORT, MSG_CANNOT_PUSH_CODE_GO_TO_FORK,
MSG_CANNOT_PUSH_CODE_SHOULD_FORK,
MSG_CANNOT_PUSH_UNSIGNED, MSG_CANNOT_PUSH_UNSIGNED,
MSG_CANNOT_PUSH_UNSIGNED_SHORT, MSG_CANNOT_PUSH_UNSIGNED_SHORT,
MSG_FORK,
MSG_GO_TO_FORK,
} from '~/ide/messages'; } from '~/ide/messages';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import * as getters from '~/ide/stores/getters'; import * as getters from '~/ide/stores/getters';
import { file } from '../helpers'; import { file } from '../helpers';
const TEST_PROJECT_ID = 'test_project'; const TEST_PROJECT_ID = 'test_project';
const TEST_IDE_PATH = '/test/ide/path';
const TEST_FORK_PATH = '/test/fork/path';
describe('IDE store getters', () => { describe('IDE store getters', () => {
let localState; let localState;
...@@ -433,14 +438,72 @@ describe('IDE store getters', () => { ...@@ -433,14 +438,72 @@ describe('IDE store getters', () => {
}); });
describe('canPushCodeStatus', () => { describe('canPushCodeStatus', () => {
it.each` it.each([
pushCode | rejectUnsignedCommits | expected [
${true} | ${false} | ${{ isAllowed: true, message: '', messageShort: '' }} 'when can push code, and can push unsigned commits',
${false} | ${false} | ${{ isAllowed: false, message: MSG_CANNOT_PUSH_CODE, messageShort: MSG_CANNOT_PUSH_CODE_SHORT }} {
${false} | ${true} | ${{ isAllowed: false, message: MSG_CANNOT_PUSH_UNSIGNED, messageShort: MSG_CANNOT_PUSH_UNSIGNED_SHORT }} input: { pushCode: true, rejectUnsignedCommits: false },
`( output: { isAllowed: true, message: '', messageShort: '' },
'with pushCode="$pushCode" and rejectUnsignedCommits="$rejectUnsignedCommits"', },
({ pushCode, rejectUnsignedCommits, expected }) => { ],
[
'when cannot push code, and can push unsigned commits',
{
input: { pushCode: false, rejectUnsignedCommits: false },
output: {
isAllowed: false,
message: MSG_CANNOT_PUSH_CODE,
messageShort: MSG_CANNOT_PUSH_CODE,
},
},
],
[
'when cannot push code, and has ide_path in forkInfo',
{
input: {
pushCode: false,
rejectUnsignedCommits: false,
forkInfo: { ide_path: TEST_IDE_PATH },
},
output: {
isAllowed: false,
message: MSG_CANNOT_PUSH_CODE_GO_TO_FORK,
messageShort: MSG_CANNOT_PUSH_CODE,
action: { href: TEST_IDE_PATH, text: MSG_GO_TO_FORK },
},
},
],
[
'when cannot push code, and has fork_path in forkInfo',
{
input: {
pushCode: false,
rejectUnsignedCommits: false,
forkInfo: { fork_path: TEST_FORK_PATH },
},
output: {
isAllowed: false,
message: MSG_CANNOT_PUSH_CODE_SHOULD_FORK,
messageShort: MSG_CANNOT_PUSH_CODE,
action: { href: TEST_FORK_PATH, text: MSG_FORK, isForm: true },
},
},
],
[
'when can push code, but cannot push unsigned commits',
{
input: { pushCode: true, rejectUnsignedCommits: true },
output: {
isAllowed: false,
message: MSG_CANNOT_PUSH_UNSIGNED,
messageShort: MSG_CANNOT_PUSH_UNSIGNED_SHORT,
},
},
],
])('%s', (testName, { input, output }) => {
const { forkInfo, rejectUnsignedCommits, pushCode } = input;
localState.links = { forkInfo };
localState.projects[TEST_PROJECT_ID] = { localState.projects[TEST_PROJECT_ID] = {
pushRules: { pushRules: {
[PUSH_RULE_REJECT_UNSIGNED_COMMITS]: rejectUnsignedCommits, [PUSH_RULE_REJECT_UNSIGNED_COMMITS]: rejectUnsignedCommits,
...@@ -451,9 +514,8 @@ describe('IDE store getters', () => { ...@@ -451,9 +514,8 @@ describe('IDE store getters', () => {
}; };
localState.currentProjectId = TEST_PROJECT_ID; localState.currentProjectId = TEST_PROJECT_ID;
expect(localStore.getters.canPushCodeStatus).toEqual(expected); expect(localStore.getters.canPushCodeStatus).toEqual(output);
}, });
);
}); });
describe('canPushCode', () => { describe('canPushCode', () => {
......
...@@ -489,9 +489,18 @@ RSpec.describe BlobHelper do ...@@ -489,9 +489,18 @@ RSpec.describe BlobHelper do
expect(uri.path).to eq("/#{project.namespace.path}/#{project.path}/-/forks") expect(uri.path).to eq("/#{project.namespace.path}/#{project.path}/-/forks")
expect(params).to include("continue[to]=/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master") expect(params).to include("continue[to]=/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master")
expect(params).to include("continue[notice]=#{edit_in_new_fork_notice}")
expect(params).to include("continue[notice_now]=#{edit_in_new_fork_notice_now}")
expect(params).to include("namespace_key=#{current_user.namespace.id}") expect(params).to include("namespace_key=#{current_user.namespace.id}")
end end
it 'does not include notice params with_notice: false' do
uri = URI(helper.ide_fork_and_edit_path(project, "master", "", with_notice: false))
expect(uri.path).to eq("/#{project.namespace.path}/#{project.path}/-/forks")
expect(CGI.unescape(uri.query)).to eq("continue[to]=/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master&namespace_key=#{current_user.namespace.id}")
end
context 'when user is not logged in' do context 'when user is not logged in' do
let(:current_user) { nil } let(:current_user) { nil }
......
...@@ -17,7 +17,7 @@ RSpec.describe IdeHelper do ...@@ -17,7 +17,7 @@ RSpec.describe IdeHelper do
'branch-name' => nil, 'branch-name' => nil,
'file-path' => nil, 'file-path' => nil,
'merge-request' => nil, 'merge-request' => nil,
'forked-project' => nil, 'fork-info' => nil,
'project' => nil 'project' => nil
) )
end end
...@@ -25,10 +25,12 @@ RSpec.describe IdeHelper do ...@@ -25,10 +25,12 @@ RSpec.describe IdeHelper do
context 'when instance vars are set' do context 'when instance vars are set' do
it 'returns instance data in the hash' do it 'returns instance data in the hash' do
fork_info = { ide_path: '/test/ide/path' }
self.instance_variable_set(:@branch, 'master') self.instance_variable_set(:@branch, 'master')
self.instance_variable_set(:@path, 'foo/bar') self.instance_variable_set(:@path, 'foo/bar')
self.instance_variable_set(:@merge_request, '1') self.instance_variable_set(:@merge_request, '1')
self.instance_variable_set(:@forked_project, project) self.instance_variable_set(:@fork_info, fork_info)
self.instance_variable_set(:@project, project) self.instance_variable_set(:@project, project)
serialized_project = API::Entities::Project.represent(project).to_json serialized_project = API::Entities::Project.represent(project).to_json
...@@ -38,7 +40,7 @@ RSpec.describe IdeHelper do ...@@ -38,7 +40,7 @@ RSpec.describe IdeHelper do
'branch-name' => 'master', 'branch-name' => 'master',
'file-path' => 'foo/bar', 'file-path' => 'foo/bar',
'merge-request' => '1', 'merge-request' => '1',
'forked-project' => serialized_project, 'fork-info' => fork_info.to_json,
'project' => serialized_project 'project' => serialized_project
) )
end end
......
...@@ -8,6 +8,7 @@ RSpec.describe IdeController do ...@@ -8,6 +8,7 @@ RSpec.describe IdeController do
let_it_be(:other_user) { create(:user) } let_it_be(:other_user) { create(:user) }
let(:user) { creator } let(:user) { creator }
let(:branch) { '' }
before do before do
sign_in(user) sign_in(user)
...@@ -28,24 +29,33 @@ RSpec.describe IdeController do ...@@ -28,24 +29,33 @@ RSpec.describe IdeController do
let(:user) { other_user } let(:user) { other_user }
context 'when user does not have fork' do context 'when user does not have fork' do
it 'does not instantiate forked_project instance var and return 200' do it 'instantiates fork_info instance var with fork_path and return 200' do
subject subject
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:project)).to eq project expect(assigns(:project)).to eq project
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to eq({ fork_path: controller.helpers.ide_fork_and_edit_path(project, branch, '', with_notice: false) })
end
it 'has nil fork_info if user cannot fork' do
project.project_feature.update!(forking_access_level: ProjectFeature::DISABLED)
subject
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:fork_info)).to be_nil
end end
end end
context 'when user has have fork' do context 'when user has fork' do
let!(:fork) { fork_project(project, user, repository: true) } let!(:fork) { fork_project(project, user, repository: true, namespace: user.namespace) }
it 'instantiates forked_project instance var and return 200' do it 'instantiates fork_info instance var with ide_path and return 200' do
subject subject
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:project)).to eq project expect(assigns(:project)).to eq project
expect(assigns(:forked_project)).to eq fork expect(assigns(:fork_info)).to eq({ ide_path: controller.helpers.ide_edit_path(fork, branch, '') })
end end
end end
end end
...@@ -61,7 +71,7 @@ RSpec.describe IdeController do ...@@ -61,7 +71,7 @@ RSpec.describe IdeController do
expect(assigns(:branch)).to be_nil expect(assigns(:branch)).to be_nil
expect(assigns(:path)).to be_nil expect(assigns(:path)).to be_nil
expect(assigns(:merge_request)).to be_nil expect(assigns(:merge_request)).to be_nil
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
end end
...@@ -76,7 +86,7 @@ RSpec.describe IdeController do ...@@ -76,7 +86,7 @@ RSpec.describe IdeController do
expect(assigns(:branch)).to be_nil expect(assigns(:branch)).to be_nil
expect(assigns(:path)).to be_nil expect(assigns(:path)).to be_nil
expect(assigns(:merge_request)).to be_nil expect(assigns(:merge_request)).to be_nil
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
end end
...@@ -91,7 +101,7 @@ RSpec.describe IdeController do ...@@ -91,7 +101,7 @@ RSpec.describe IdeController do
expect(assigns(:branch)).to be_nil expect(assigns(:branch)).to be_nil
expect(assigns(:path)).to be_nil expect(assigns(:path)).to be_nil
expect(assigns(:merge_request)).to be_nil expect(assigns(:merge_request)).to be_nil
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
it_behaves_like 'user cannot push code' it_behaves_like 'user cannot push code'
...@@ -108,55 +118,58 @@ RSpec.describe IdeController do ...@@ -108,55 +118,58 @@ RSpec.describe IdeController do
expect(assigns(:branch)).to be_nil expect(assigns(:branch)).to be_nil
expect(assigns(:path)).to be_nil expect(assigns(:path)).to be_nil
expect(assigns(:merge_request)).to be_nil expect(assigns(:merge_request)).to be_nil
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
it_behaves_like 'user cannot push code' it_behaves_like 'user cannot push code'
context "/-/ide/project/:project/#{action}/:branch" do context "/-/ide/project/:project/#{action}/:branch" do
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/master" } let(:branch) { 'master' }
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}" }
it 'instantiates project and branch instance vars and return 200' do it 'instantiates project and branch instance vars and return 200' do
subject subject
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:project)).to eq project expect(assigns(:project)).to eq project
expect(assigns(:branch)).to eq 'master' expect(assigns(:branch)).to eq branch
expect(assigns(:path)).to be_nil expect(assigns(:path)).to be_nil
expect(assigns(:merge_request)).to be_nil expect(assigns(:merge_request)).to be_nil
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
it_behaves_like 'user cannot push code' it_behaves_like 'user cannot push code'
context "/-/ide/project/:project/#{action}/:branch/-" do context "/-/ide/project/:project/#{action}/:branch/-" do
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/branch/slash/-" } let(:branch) { 'branch/slash' }
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}/-" }
it 'instantiates project and branch instance vars and return 200' do it 'instantiates project and branch instance vars and return 200' do
subject subject
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:project)).to eq project expect(assigns(:project)).to eq project
expect(assigns(:branch)).to eq 'branch/slash' expect(assigns(:branch)).to eq branch
expect(assigns(:path)).to be_nil expect(assigns(:path)).to be_nil
expect(assigns(:merge_request)).to be_nil expect(assigns(:merge_request)).to be_nil
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
it_behaves_like 'user cannot push code' it_behaves_like 'user cannot push code'
context "/-/ide/project/:project/#{action}/:branch/-/:path" do context "/-/ide/project/:project/#{action}/:branch/-/:path" do
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/master/-/foo/.bar" } let(:branch) { 'master' }
let(:route) { "/-/ide/project/#{project.full_path}/#{action}/#{branch}/-/foo/.bar" }
it 'instantiates project, branch, and path instance vars and return 200' do it 'instantiates project, branch, and path instance vars and return 200' do
subject subject
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:project)).to eq project expect(assigns(:project)).to eq project
expect(assigns(:branch)).to eq 'master' expect(assigns(:branch)).to eq branch
expect(assigns(:path)).to eq 'foo/.bar' expect(assigns(:path)).to eq 'foo/.bar'
expect(assigns(:merge_request)).to be_nil expect(assigns(:merge_request)).to be_nil
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
it_behaves_like 'user cannot push code' it_behaves_like 'user cannot push code'
...@@ -179,7 +192,7 @@ RSpec.describe IdeController do ...@@ -179,7 +192,7 @@ RSpec.describe IdeController do
expect(assigns(:branch)).to be_nil expect(assigns(:branch)).to be_nil
expect(assigns(:path)).to be_nil expect(assigns(:path)).to be_nil
expect(assigns(:merge_request)).to eq merge_request.id.to_s expect(assigns(:merge_request)).to eq merge_request.id.to_s
expect(assigns(:forked_project)).to be_nil expect(assigns(:fork_info)).to be_nil
end end
it_behaves_like 'user cannot push code' it_behaves_like 'user cannot push code'
......
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