diff --git a/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.test.tsx b/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.test.tsx
index 635c17d9060..f27303cc447 100644
--- a/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.test.tsx
+++ b/frontend/dashboard/components/MakeCopyModal/MakeCopyModal.test.tsx
@@ -12,6 +12,13 @@ import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { QueryKey } from 'app-shared/types/QueryKey';
import { PackagesRouter } from 'app-shared/navigation/PackagesRouter';
import { app, org } from '@studio/testing/testids';
+import { useUserOrgPermissionQuery } from '../../hooks/queries/useUserOrgPermissionsQuery';
+
+jest.mock('../../hooks/queries/useUserOrgPermissionsQuery');
+
+(useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
+ data: { canCreateOrgRepo: true },
+});
const mockServiceFullName: string = `${org}/${app}`;
const mockUser: User = {
diff --git a/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.test.tsx b/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.test.tsx
index 1bbcc5a319a..ef510bada1f 100644
--- a/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.test.tsx
+++ b/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.test.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { render, screen } from '@testing-library/react';
+import { screen } from '@testing-library/react';
import {
NewApplicationForm,
type NewApplicationFormProps,
@@ -8,7 +8,11 @@ import {
import { type User } from 'app-shared/types/Repository';
import { type Organization } from 'app-shared/types/Organization';
import userEvent from '@testing-library/user-event';
-import { textMock } from '../../../testing/mocks/i18nMock';
+import { textMock } from '@studio/testing/mocks/i18nMock';
+import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
+import { renderWithProviders } from '../../testing/mocks';
+import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
+import { useUserOrgPermissionQuery } from '../../hooks/queries/useUserOrgPermissionsQuery';
const mockOnSubmit = jest.fn();
@@ -51,12 +55,18 @@ const defaultProps: NewApplicationFormProps = {
actionableElement: mockCancelComponentButton,
};
+jest.mock('../../hooks/queries/useUserOrgPermissionsQuery');
+
+(useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
+ data: { canCreateOrgRepo: true },
+});
+
describe('NewApplicationForm', () => {
afterEach(jest.clearAllMocks);
it('calls onSubmit when form is submitted with valid data', async () => {
const user = userEvent.setup();
- render();
+ renderNewApplicationForm();
const select = screen.getByLabelText(textMock('general.service_owner'));
await user.click(select);
@@ -81,7 +91,7 @@ describe('NewApplicationForm', () => {
it('does not call onSubmit when form is submitted with invalid data', async () => {
const user = userEvent.setup();
- render();
+ renderNewApplicationForm();
const select = screen.getByLabelText(textMock('general.service_owner'));
await user.click(select);
@@ -96,4 +106,47 @@ describe('NewApplicationForm', () => {
expect(mockOnSubmit).toHaveBeenCalledTimes(0);
});
+
+ it('should notify the user if they lack permission to create a new application for the organization and disable the "Create" button', async () => {
+ const user = userEvent.setup();
+ (useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
+ data: { canCreateOrgRepo: false },
+ });
+ renderNewApplicationForm(defaultProps);
+
+ const serviceOwnerSelect = screen.getByLabelText(textMock('general.service_owner'));
+ await user.selectOptions(serviceOwnerSelect, mockOrg.username);
+ expect(
+ await screen.findByText(textMock('dashboard.missing_service_owner_rights_error_message')),
+ ).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: mockSubmitbuttonText })).toBeDisabled();
+ });
+
+ it('should enable the "Create" button and not display an error if the user has permission to create an organization', async () => {
+ const user = userEvent.setup();
+ (useUserOrgPermissionQuery as jest.Mock).mockReturnValue({
+ data: { canCreateOrgRepo: true },
+ });
+ renderNewApplicationForm(defaultProps);
+
+ const serviceOwnerSelect = screen.getByLabelText(textMock('general.service_owner'));
+ await user.selectOptions(serviceOwnerSelect, mockOrg.username);
+ expect(
+ screen.queryByText(textMock('dashboard.missing_service_owner_rights_error_message')),
+ ).not.toBeInTheDocument();
+ expect(screen.getByRole('button', { name: mockSubmitbuttonText })).toBeEnabled();
+ });
});
+
+function renderNewApplicationForm(
+ newApplicationFormProps?: Partial,
+ services?: Partial,
+) {
+ return renderWithProviders(
+ ,
+ {
+ queries: services,
+ queryClient: createQueryClientMock(),
+ },
+ );
+}
diff --git a/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.tsx b/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.tsx
index e3947330491..9d4c2f2feba 100644
--- a/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.tsx
+++ b/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.tsx
@@ -1,4 +1,4 @@
-import React, { type FormEvent, type ChangeEvent } from 'react';
+import React, { type FormEvent, type ChangeEvent, useState } from 'react';
import classes from './NewApplicationForm.module.css';
import { StudioButton, StudioSpinner } from '@studio/components';
import { useTranslation } from 'react-i18next';
@@ -11,6 +11,7 @@ import { SelectedContextType } from 'dashboard/context/HeaderContext';
import { type NewAppForm } from '../../types/NewAppForm';
import { useCreateAppFormValidation } from './hooks/useCreateAppFormValidation';
import { Link } from 'react-router-dom';
+import { useUserOrgPermissionQuery } from '../../hooks/queries/useUserOrgPermissionsQuery';
type CancelButton = {
onClick: () => void;
@@ -47,11 +48,14 @@ export const NewApplicationForm = ({
const { t } = useTranslation();
const selectedContext = useSelectedContext();
const { validateRepoOwnerName, validateRepoName } = useCreateAppFormValidation();
-
const defaultSelectedOrgOrUser: string =
selectedContext === SelectedContextType.Self || selectedContext === SelectedContextType.All
? user.login
: selectedContext;
+ const [currentSelectedOrg, setCurrentSelectedOrg] = useState(defaultSelectedOrgOrUser);
+ const { data: userOrgPermission, isFetching } = useUserOrgPermissionQuery(currentSelectedOrg, {
+ enabled: Boolean(currentSelectedOrg),
+ });
const validateTextValue = (event: ChangeEvent) => {
const { errorMessage: repoNameErrorMessage, isValid: isRepoNameValid } = validateRepoName(
@@ -96,14 +100,22 @@ export const NewApplicationForm = ({
return isOrgValid && isRepoNameValid;
};
+ const createRepoAccessError: string =
+ !userOrgPermission?.canCreateOrgRepo && !isFetching
+ ? t('dashboard.missing_service_owner_rights_error_message')
+ : '';
+
+ const hasCreateRepoAccessError: boolean = Boolean(createRepoAccessError);
+
return (