diff --git a/app/assets/javascripts/admin/organizations/index/components/app.vue b/app/assets/javascripts/admin/organizations/index/components/app.vue index c2a3f016f71ac2040ddc6ec260223d7f10a47c7b..de97fa478db6fb401878fa5f7dfb3c45865678a5 100644 --- a/app/assets/javascripts/admin/organizations/index/components/app.vue +++ b/app/assets/javascripts/admin/organizations/index/components/app.vue @@ -46,6 +46,9 @@ export default { showHeader() { return this.loading || this.organizations.nodes?.length; }, + showNewOrganizationButton() { + return gon.features?.allowOrganizationCreation; + }, loading() { return this.$apollo.queries.organizations.loading; }, @@ -78,7 +81,7 @@ export default { class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-mb-5" > <h1 class="gl-m-0 gl-font-size-h-display">{{ $options.i18n.pageTitle }}</h1> - <gl-button :href="newOrganizationUrl" variant="confirm">{{ + <gl-button v-if="showNewOrganizationButton" :href="newOrganizationUrl" variant="confirm">{{ $options.i18n.newOrganization }}</gl-button> </div> diff --git a/app/assets/javascripts/organizations/shared/components/organizations_view.vue b/app/assets/javascripts/organizations/shared/components/organizations_view.vue index 4fa17d4cffd0395cc0986aaf3b540af0bca6f505..62434de9c88f7901f31a30ab7d678e51f9473665 100644 --- a/app/assets/javascripts/organizations/shared/components/organizations_view.vue +++ b/app/assets/javascripts/organizations/shared/components/organizations_view.vue @@ -34,6 +34,24 @@ export default { nodes() { return this.organizations.nodes || []; }, + emptyStateProps() { + const baseProps = { + svgHeight: 144, + svgPath: this.organizationsEmptyStateSvgPath, + title: this.$options.i18n.emptyStateTitle, + description: this.$options.i18n.emptyStateDescription, + }; + + if (gon.features?.allowOrganizationCreation) { + return { + ...baseProps, + primaryButtonLink: this.newOrganizationUrl, + primaryButtonText: this.$options.i18n.emptyStateButtonText, + }; + } + + return baseProps; + }, }, }; </script> @@ -47,13 +65,5 @@ export default { @prev="$emit('prev', $event)" @next="$emit('next', $event)" /> - <gl-empty-state - v-else - :svg-height="144" - :svg-path="organizationsEmptyStateSvgPath" - :title="$options.i18n.emptyStateTitle" - :description="$options.i18n.emptyStateDescription" - :primary-button-link="newOrganizationUrl" - :primary-button-text="$options.i18n.emptyStateButtonText" - /> + <gl-empty-state v-else v-bind="emptyStateProps" /> </template> diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb index 55895f8bfac26f4d804a8e99f204ed32e4a7b6c1..dc48fe7507ed0ece3c66f2d0858c272576a95b0e 100644 --- a/app/controllers/admin/organizations_controller.rb +++ b/app/controllers/admin/organizations_controller.rb @@ -5,6 +5,9 @@ class OrganizationsController < ApplicationController feature_category :cell before_action :check_feature_flag! + before_action only: [:index] do + push_frontend_feature_flag(:allow_organization_creation, current_user) + end def index; end diff --git a/app/controllers/organizations/application_controller.rb b/app/controllers/organizations/application_controller.rb index b4a050835c0d56cd4af78d7b8ef433eec93eba22..180b114370b7fd4c0d244839bb540c752c6ebb53 100644 --- a/app/controllers/organizations/application_controller.rb +++ b/app/controllers/organizations/application_controller.rb @@ -21,6 +21,7 @@ def check_feature_flag! end def authorize_create_organization! + access_denied! unless Feature.enabled?(:allow_organization_creation, current_user) access_denied! unless can?(current_user, :create_organization) end diff --git a/spec/frontend/admin/organizations/index/components/app_spec.js b/spec/frontend/admin/organizations/index/components/app_spec.js index 00eda36608cd383f90521746f1c98f7d61c87fe9..72339303e25f3800940bede04994be2e604d8a70 100644 --- a/spec/frontend/admin/organizations/index/components/app_spec.js +++ b/spec/frontend/admin/organizations/index/components/app_spec.js @@ -47,6 +47,10 @@ describe('AdminOrganizationsIndexApp', () => { }); }; + beforeEach(() => { + gon.features = { allowOrganizationCreation: true }; + }); + afterEach(() => { mockApollo = null; }); @@ -119,6 +123,17 @@ describe('AdminOrganizationsIndexApp', () => { }); }); + describe('when `allowOrganizationCreation` feature flag is disabled', () => { + beforeEach(() => { + gon.features = { allowOrganizationCreation: false }; + + createComponent(); + return waitForPromises(); + }); + + itDoesNotRenderNewOrganizationButton(); + }); + describe('when API call is successful and returns no organizations', () => { beforeEach(async () => { createComponent( diff --git a/spec/frontend/organizations/shared/components/organizations_view_spec.js b/spec/frontend/organizations/shared/components/organizations_view_spec.js index 7592ca30493a71dae192e07d838fbbb1d4c808e5..270998eba1f79159b3c01954b4cfd59835018bb7 100644 --- a/spec/frontend/organizations/shared/components/organizations_view_spec.js +++ b/spec/frontend/organizations/shared/components/organizations_view_spec.js @@ -24,6 +24,10 @@ describe('OrganizationsView', () => { const findOrganizationsList = () => wrapper.findComponent(OrganizationsList); const findGlEmptyState = () => wrapper.findComponent(GlEmptyState); + beforeEach(() => { + gon.features = { allowOrganizationCreation: true }; + }); + describe.each` description | loading | orgsData | emptyStateSvg | emptyStateUrl ${'when loading'} | ${true} | ${[]} | ${false} | ${false} @@ -55,6 +59,18 @@ describe('OrganizationsView', () => { }); }); + describe('when `allowOrganizationCreation` feature flag is disabled', () => { + beforeEach(() => { + gon.features = { allowOrganizationCreation: false }; + createComponent({ loading: false, organizations: { nodes: [], pageInfo: {} } }); + }); + + it('does not render `New organization` button in empty state', () => { + expect(findGlEmptyState().attributes('primarybuttonlink')).toBeUndefined(); + expect(findGlEmptyState().attributes('primarybuttontext')).toBeUndefined(); + }); + }); + describe('when `OrganizationsList` emits `next` event', () => { const endCursor = 'mockEndCursor'; diff --git a/spec/requests/organizations/organizations_controller_spec.rb b/spec/requests/organizations/organizations_controller_spec.rb index ba8edfcf05e7649e8c0d7393ba15b2106819f60d..114c2e6b6607683839008d3bd8ce2fdfc7d62df3 100644 --- a/spec/requests/organizations/organizations_controller_spec.rb +++ b/spec/requests/organizations/organizations_controller_spec.rb @@ -153,6 +153,17 @@ subject(:gitlab_request) { get new_organization_path } it_behaves_like 'controller action that requires authentication by any user' + + context 'when user is signed in and `allow_organization_creation` feature flag is disabled' do + let_it_be(:user) { create(:user) } + + before do + stub_feature_flags(allow_organization_creation: false) + sign_in(user) + end + + it_behaves_like 'organization - not found response' + end end describe 'GET #index' do