From f059f933dad0895ac5cfa42f8f513a8014cd7c42 Mon Sep 17 00:00:00 2001 From: Peter Hegman <phegman@gitlab.com> Date: Mon, 1 Apr 2024 16:08:07 -0700 Subject: [PATCH] Check `allow_organization_creation` feature flag on a few more views Check in admin area and empty state. Also 404 new organization page if disabled. --- .../organizations/index/components/app.vue | 5 +++- .../shared/components/organizations_view.vue | 28 +++++++++++++------ .../admin/organizations_controller.rb | 3 ++ .../organizations/application_controller.rb | 1 + .../index/components/app_spec.js | 15 ++++++++++ .../components/organizations_view_spec.js | 16 +++++++++++ .../organizations_controller_spec.rb | 11 ++++++++ 7 files changed, 69 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/admin/organizations/index/components/app.vue b/app/assets/javascripts/admin/organizations/index/components/app.vue index c2a3f016f71ac..de97fa478db6f 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 4fa17d4cffd03..62434de9c88f7 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 55895f8bfac26..dc48fe7507ed0 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 b4a050835c0d5..180b114370b7f 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 00eda36608cd3..72339303e25f3 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 7592ca30493a7..270998eba1f79 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 ba8edfcf05e76..114c2e6b66076 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 -- GitLab