Commit a617e2f1 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch...

Merge branch '264262-users-who-have-reached-their-personal-project-limit-cannot-fork-to-group' into 'master'

Allow users to fork to a group when their personal namespace is full

See merge request gitlab-org/gitlab!53632
parents 6d5420c4 ec4d1ddb
...@@ -14,10 +14,6 @@ export default { ...@@ -14,10 +14,6 @@ export default {
ForkGroupsListItem, ForkGroupsListItem,
}, },
props: { props: {
hasReachedProjectLimit: {
type: Boolean,
required: true,
},
endpoint: { endpoint: {
type: String, type: String,
required: true, required: true,
...@@ -77,7 +73,6 @@ export default { ...@@ -77,7 +73,6 @@ export default {
v-for="(namespace, index) in filteredNamespaces" v-for="(namespace, index) in filteredNamespaces"
:key="index" :key="index"
:group="namespace" :group="namespace"
:has-reached-project-limit="hasReachedProjectLimit"
/> />
</ul> </ul>
</gl-tab> </gl-tab>
......
...@@ -10,7 +10,6 @@ import { ...@@ -10,7 +10,6 @@ import {
GlSafeHtmlDirective as SafeHtml, GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants'; import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '~/groups/constants';
import { __ } from '~/locale';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
export default { export default {
...@@ -31,10 +30,6 @@ export default { ...@@ -31,10 +30,6 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
hasReachedProjectLimit: {
type: Boolean,
required: true,
},
}, },
data() { data() {
return { namespaces: null, isForking: false }; return { namespaces: null, isForking: false };
...@@ -60,12 +55,7 @@ export default { ...@@ -60,12 +55,7 @@ export default {
return GROUP_VISIBILITY_TYPE[this.group.visibility]; return GROUP_VISIBILITY_TYPE[this.group.visibility];
}, },
isSelectButtonDisabled() { isSelectButtonDisabled() {
return this.hasReachedProjectLimit || !this.group.can_create_project; return !this.group.can_create_project;
},
selectButtonDisabledTooltip() {
return this.hasReachedProjectLimit
? this.$options.i18n.hasReachedProjectLimitMessage
: this.$options.i18n.insufficientPermissionsMessage;
}, },
}, },
...@@ -76,13 +66,6 @@ export default { ...@@ -76,13 +66,6 @@ export default {
}, },
}, },
i18n: {
hasReachedProjectLimitMessage: __('You have reached your project limit'),
insufficientPermissionsMessage: __(
'You must have permission to create a project in a namespace before forking.',
),
},
csrf, csrf,
}; };
</script> </script>
...@@ -149,7 +132,9 @@ export default { ...@@ -149,7 +132,9 @@ export default {
</form> </form>
</div> </div>
<gl-tooltip v-if="isSelectButtonDisabled" :target="() => $refs.selectButtonWrapper"> <gl-tooltip v-if="isSelectButtonDisabled" :target="() => $refs.selectButtonWrapper">
{{ selectButtonDisabledTooltip }} {{
__('You must have permission to create a project in a namespace before forking.')
}}
</gl-tooltip> </gl-tooltip>
</template> </template>
</div> </div>
......
import Vue from 'vue'; import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import ForkGroupsList from './components/fork_groups_list.vue'; import ForkGroupsList from './components/fork_groups_list.vue';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const mountElement = document.getElementById('fork-groups-mount-element'); const mountElement = document.getElementById('fork-groups-mount-element');
const { endpoint, canCreateProject } = mountElement.dataset; const { endpoint } = mountElement.dataset;
const hasReachedProjectLimit = !parseBoolean(canCreateProject);
return new Vue({ return new Vue({
el: mountElement, el: mountElement,
...@@ -15,7 +12,6 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -15,7 +12,6 @@ document.addEventListener('DOMContentLoaded', () => {
return h(ForkGroupsList, { return h(ForkGroupsList, {
props: { props: {
endpoint, endpoint,
hasReachedProjectLimit,
}, },
}); });
}, },
......
...@@ -14,5 +14,5 @@ ...@@ -14,5 +14,5 @@
%h5.gl-mt-0.gl-mb-0.gl-ml-3.gl-mr-3 %h5.gl-mt-0.gl-mb-0.gl-ml-3.gl-mr-3
= _("Select a namespace to fork the project") = _("Select a namespace to fork the project")
= render 'fork_button', namespace: @own_namespace = render 'fork_button', namespace: @own_namespace
#fork-groups-mount-element{ data: { endpoint: new_project_fork_path(@project, format: :json), can_create_project: current_user.can_create_project?.to_s } } #fork-groups-mount-element{ data: { endpoint: new_project_fork_path(@project, format: :json) } }
---
title: Allow users to fork to a group when their personal namespace is full
merge_request: 53632
author:
type: fixed
...@@ -9,24 +9,47 @@ RSpec.describe 'Project fork' do ...@@ -9,24 +9,47 @@ RSpec.describe 'Project fork' do
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
before do before do
sign_in user sign_in(user)
end end
it 'allows user to fork project' do it 'allows user to fork project from the project page' do
visit project_path(project) visit project_path(project)
expect(page).not_to have_css('a.disabled', text: 'Select') expect(page).not_to have_css('a.disabled', text: 'Fork')
end end
it 'disables fork button when user has exceeded project limit' do context 'user has exceeded personal project limit' do
user.projects_limit = 0 before do
user.save! user.update!(projects_limit: 0)
end
it 'disables fork button on project page' do
visit project_path(project) visit project_path(project)
expect(page).to have_css('a.disabled', text: 'Fork') expect(page).to have_css('a.disabled', text: 'Fork')
end end
context 'with a group to fork to' do
let!(:group) { create(:group).tap { |group| group.add_owner(user) } }
it 'enables fork button on project page' do
visit project_path(project)
expect(page).not_to have_css('a.disabled', text: 'Fork')
end
it 'allows user to fork only to the group on fork page', :js do
visit new_project_fork_path(project)
to_personal_namespace = find('[data-qa-selector=fork_namespace_button].disabled')
to_group = find(".fork-groups button[data-qa-name=#{group.name}]")
expect(to_personal_namespace).not_to be_nil
expect(to_group).not_to be_disabled
end
end
end
context 'forking enabled / disabled in project settings' do context 'forking enabled / disabled in project settings' do
before do before do
project.project_feature.update_attribute( project.project_feature.update_attribute(
......
...@@ -5,10 +5,6 @@ import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_group ...@@ -5,10 +5,6 @@ import ForkGroupsListItem from '~/pages/projects/forks/new/components/fork_group
describe('Fork groups list item component', () => { describe('Fork groups list item component', () => {
let wrapper; let wrapper;
const DEFAULT_PROPS = {
hasReachedProjectLimit: false,
};
const DEFAULT_GROUP_DATA = { const DEFAULT_GROUP_DATA = {
id: 22, id: 22,
name: 'Gitlab Org', name: 'Gitlab Org',
...@@ -33,7 +29,6 @@ describe('Fork groups list item component', () => { ...@@ -33,7 +29,6 @@ describe('Fork groups list item component', () => {
const createWrapper = (propsData) => { const createWrapper = (propsData) => {
wrapper = shallowMount(ForkGroupsListItem, { wrapper = shallowMount(ForkGroupsListItem, {
propsData: { propsData: {
...DEFAULT_PROPS,
...propsData, ...propsData,
}, },
}); });
......
...@@ -16,7 +16,6 @@ describe('Fork groups list component', () => { ...@@ -16,7 +16,6 @@ describe('Fork groups list component', () => {
const DEFAULT_PROPS = { const DEFAULT_PROPS = {
endpoint: '/dummy', endpoint: '/dummy',
hasReachedProjectLimit: false,
}; };
const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args); const replyWith = (...args) => axiosMock.onGet(DEFAULT_PROPS.endpoint).reply(...args);
...@@ -94,10 +93,9 @@ describe('Fork groups list component', () => { ...@@ -94,10 +93,9 @@ describe('Fork groups list component', () => {
it('renders list items for each available group', async () => { it('renders list items for each available group', async () => {
const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }]; const namespaces = [{ name: 'dummy1' }, { name: 'dummy2' }, { name: 'otherdummy' }];
const hasReachedProjectLimit = true;
replyWith(200, { namespaces }); replyWith(200, { namespaces });
createWrapper({ hasReachedProjectLimit }); createWrapper();
await waitForPromises(); await waitForPromises();
...@@ -106,7 +104,6 @@ describe('Fork groups list component', () => { ...@@ -106,7 +104,6 @@ describe('Fork groups list component', () => {
namespaces.forEach((namespace, idx) => { namespaces.forEach((namespace, idx) => {
expect(wrapper.findAll(ForkGroupsListItem).at(idx).props()).toStrictEqual({ expect(wrapper.findAll(ForkGroupsListItem).at(idx).props()).toStrictEqual({
group: namespace, group: namespace,
hasReachedProjectLimit,
}); });
}); });
}); });
......
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