From e3d174521cd988358f4acbe28eaf2e161dafe850 Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Thu, 9 Jan 2025 13:13:02 +0100 Subject: [PATCH 01/15] feat(desinger): endpoint to get UserRepositoryPermissions --- .../Designer/Controllers/UserController.cs | 13 +++++- .../Infrastructure/ServiceRegistration.cs | 1 + .../Models/Dto/UserRepositoryPermission.cs | 6 +++ .../Designer/RepositoryClient/Model/Team.cs | 2 + .../Services/Implementation/UserService.cs | 44 +++++++++++++++++++ .../Services/Interfaces/IUserService.cs | 9 ++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 backend/src/Designer/Models/Dto/UserRepositoryPermission.cs create mode 100644 backend/src/Designer/Services/Implementation/UserService.cs create mode 100644 backend/src/Designer/Services/Interfaces/IUserService.cs diff --git a/backend/src/Designer/Controllers/UserController.cs b/backend/src/Designer/Controllers/UserController.cs index 5173cdb193d..888b2b3d6c8 100644 --- a/backend/src/Designer/Controllers/UserController.cs +++ b/backend/src/Designer/Controllers/UserController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Altinn.Studio.Designer.Models.Dto; using Altinn.Studio.Designer.Services.Interfaces; using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Authorization; @@ -17,16 +18,18 @@ public class UserController : ControllerBase { private readonly IGitea _giteaApi; private readonly IAntiforgery _antiforgery; + private readonly IUserService _userService; /// /// Initializes a new instance of the class. /// /// the gitea wrapper /// Access to the antiforgery system in .NET Core - public UserController(IGitea giteaWrapper, IAntiforgery antiforgery) + public UserController(IGitea giteaWrapper, IAntiforgery antiforgery, IUserService userService) { _giteaApi = giteaWrapper; _antiforgery = antiforgery; + _userService = userService; } /// @@ -80,6 +83,14 @@ public async Task PutStarred(string org, string repository) return success ? NoContent() : StatusCode(418); } + [HttpGet] + [Route("org-permissions/{org}")] + public async Task HasAccessToCreateRepository(string org) + { + UserRepositoryPermission userRepository = await _userService.GetUserRepositoryPermission(org); + return Ok(userRepository); + } + /// /// Removes the star marking on the specified repository. /// diff --git a/backend/src/Designer/Infrastructure/ServiceRegistration.cs b/backend/src/Designer/Infrastructure/ServiceRegistration.cs index 5d3b0607d8b..d9d0d681aea 100644 --- a/backend/src/Designer/Infrastructure/ServiceRegistration.cs +++ b/backend/src/Designer/Infrastructure/ServiceRegistration.cs @@ -54,6 +54,7 @@ public static IServiceCollection RegisterServiceImplementations(this IServiceCol services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/backend/src/Designer/Models/Dto/UserRepositoryPermission.cs b/backend/src/Designer/Models/Dto/UserRepositoryPermission.cs new file mode 100644 index 00000000000..b3ab9081b82 --- /dev/null +++ b/backend/src/Designer/Models/Dto/UserRepositoryPermission.cs @@ -0,0 +1,6 @@ +namespace Altinn.Studio.Designer.Models.Dto; + +public class UserRepositoryPermission +{ + public bool CanCreateOrgRepo { get; set; } +} diff --git a/backend/src/Designer/RepositoryClient/Model/Team.cs b/backend/src/Designer/RepositoryClient/Model/Team.cs index 17711303a0b..4faeee5b783 100644 --- a/backend/src/Designer/RepositoryClient/Model/Team.cs +++ b/backend/src/Designer/RepositoryClient/Model/Team.cs @@ -10,6 +10,8 @@ public class Team /// public string Name { get; set; } + public bool can_create_org_repo { get; set; } + /// /// The organization that owns the team /// diff --git a/backend/src/Designer/Services/Implementation/UserService.cs b/backend/src/Designer/Services/Implementation/UserService.cs new file mode 100644 index 00000000000..016a2989c6a --- /dev/null +++ b/backend/src/Designer/Services/Implementation/UserService.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Helpers; +using Altinn.Studio.Designer.Models.Dto; +using Altinn.Studio.Designer.RepositoryClient.Model; +using Altinn.Studio.Designer.Services.Interfaces; +using Microsoft.AspNetCore.Http; + +namespace Altinn.Studio.Designer.Services.Implementation; + +public class UserService : IUserService +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IGitea _giteaApi; + + public UserService(IHttpContextAccessor httpContextAccessor, IGitea giteaApi) + { + _httpContextAccessor = httpContextAccessor; + _giteaApi = giteaApi; + } + + public async Task GetUserRepositoryPermission(string org) + { + bool canCreateOrgRepo = await HasPermissionToCreateOrgRepo(org); + return new UserRepositoryPermission() { CanCreateOrgRepo = canCreateOrgRepo }; + } + + private bool IsUserSelfOrg(string org) + { + return AuthenticationHelper.GetDeveloperUserName(_httpContextAccessor.HttpContext) == org; + } + + private async Task HasPermissionToCreateOrgRepo(string org) + { + List teams = await _giteaApi.GetTeams(); + return IsUserSelfOrg(org) || teams.Any(team => CheckPermissionToCreateOrgRepo(team, org)); + } + + private static bool CheckPermissionToCreateOrgRepo(Team team, string org) + { + return team.can_create_org_repo & team.Organization.Username == org; + } +} diff --git a/backend/src/Designer/Services/Interfaces/IUserService.cs b/backend/src/Designer/Services/Interfaces/IUserService.cs new file mode 100644 index 00000000000..db06016472c --- /dev/null +++ b/backend/src/Designer/Services/Interfaces/IUserService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Altinn.Studio.Designer.Models.Dto; + +namespace Altinn.Studio.Designer.Services.Interfaces; + +public interface IUserService +{ + public Task GetUserRepositoryPermission(string org); +} From 698e3345f83e4a136fe2582c93cbe8ec8cc4e5ce Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Thu, 9 Jan 2025 15:16:46 +0100 Subject: [PATCH 02/15] unit-test for UserService --- .../Services/Interfaces/IUserService.cs | 2 +- .../Services/UserServiceTests.cs | 49 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 backend/tests/Designer.Tests/Services/UserServiceTests.cs diff --git a/backend/src/Designer/Services/Interfaces/IUserService.cs b/backend/src/Designer/Services/Interfaces/IUserService.cs index db06016472c..270fe89d8db 100644 --- a/backend/src/Designer/Services/Interfaces/IUserService.cs +++ b/backend/src/Designer/Services/Interfaces/IUserService.cs @@ -5,5 +5,5 @@ namespace Altinn.Studio.Designer.Services.Interfaces; public interface IUserService { - public Task GetUserRepositoryPermission(string org); + public Task GetUserRepositoryPermission(string org); } diff --git a/backend/tests/Designer.Tests/Services/UserServiceTests.cs b/backend/tests/Designer.Tests/Services/UserServiceTests.cs new file mode 100644 index 00000000000..8665a48225a --- /dev/null +++ b/backend/tests/Designer.Tests/Services/UserServiceTests.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Altinn.Studio.Designer.Services.Implementation; +using Altinn.Studio.Designer.RepositoryClient.Model; +using Altinn.Studio.Designer.Services.Interfaces; +using Microsoft.AspNetCore.Http; +using Moq; +using Xunit; + +namespace Designer.Tests.Services +{ + public class UserServiceTests + { + private readonly Mock _httpContextAccessor; + private readonly Mock _giteaApi; + + public UserServiceTests() + { + _httpContextAccessor = new Mock(); + _giteaApi = new Mock(); + var context = new DefaultHttpContext(); + _httpContextAccessor.Setup(req => req.HttpContext).Returns(context); + } + + [Theory] + [InlineData( "org1", false)] + [InlineData( "org2", true)] + public async Task GetUserRepositoryPermission_ReturnsCorrectPermission(string org, bool expectedCanCreate) + { + var teams = new List + { + new Team + { + Organization = new Organization { Username = org }, + can_create_org_repo = expectedCanCreate + } + }; + + _giteaApi.Setup(api => api.GetTeams()).ReturnsAsync(teams); + + var userService = new UserService(_httpContextAccessor.Object, _giteaApi.Object); + + var result = await userService.GetUserRepositoryPermission(org); + + Assert.NotNull(result); + Assert.Equal(expectedCanCreate, result.CanCreateOrgRepo); + } + } +} From 29c84384bac3dc69f9b82618be4208066967a367 Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Thu, 9 Jan 2025 18:56:19 +0100 Subject: [PATCH 03/15] dotnet format --- backend/tests/Designer.Tests/Services/UserServiceTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/tests/Designer.Tests/Services/UserServiceTests.cs b/backend/tests/Designer.Tests/Services/UserServiceTests.cs index 8665a48225a..72ce750ed97 100644 --- a/backend/tests/Designer.Tests/Services/UserServiceTests.cs +++ b/backend/tests/Designer.Tests/Services/UserServiceTests.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Altinn.Studio.Designer.Services.Implementation; using Altinn.Studio.Designer.RepositoryClient.Model; +using Altinn.Studio.Designer.Services.Implementation; using Altinn.Studio.Designer.Services.Interfaces; using Microsoft.AspNetCore.Http; using Moq; @@ -23,8 +23,8 @@ public UserServiceTests() } [Theory] - [InlineData( "org1", false)] - [InlineData( "org2", true)] + [InlineData("org1", false)] + [InlineData("org2", true)] public async Task GetUserRepositoryPermission_ReturnsCorrectPermission(string org, bool expectedCanCreate) { var teams = new List From dc033af671ad852a35e9fbc6422ba42db169e101 Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Thu, 9 Jan 2025 19:10:05 +0100 Subject: [PATCH 04/15] added unit-test --- .../UserControllerGiteaIntegrationTests.cs | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs index e2576cb7adf..86630fa5d3c 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; +using Altinn.Studio.Designer.Models.Dto; using Altinn.Studio.Designer.RepositoryClient.Model; using Designer.Tests.Fixtures; using Designer.Tests.Utils; @@ -14,8 +15,9 @@ namespace Designer.Tests.GiteaIntegrationTests { public class UserControllerGiteaIntegrationTests : GiteaIntegrationTestsBase { - - public UserControllerGiteaIntegrationTests(GiteaWebAppApplicationFactoryFixture factory, GiteaFixture giteaFixture, SharedDesignerHttpClientProvider sharedDesignerHttpClientProvider) : base(factory, giteaFixture, sharedDesignerHttpClientProvider) + public UserControllerGiteaIntegrationTests(GiteaWebAppApplicationFactoryFixture factory, + GiteaFixture giteaFixture, SharedDesignerHttpClientProvider sharedDesignerHttpClientProvider) : base( + factory, giteaFixture, sharedDesignerHttpClientProvider) { } @@ -31,10 +33,8 @@ public async Task GetCurrentUser_ShouldReturnOk(string expectedUserName, string response.StatusCode.Should().Be(HttpStatusCode.OK); response.Headers.First(h => h.Key == "Set-Cookie").Value.Should().Contain(e => e.Contains("XSRF-TOKEN")); string content = await response.Content.ReadAsStringAsync(); - var user = JsonSerializer.Deserialize(content, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); + var user = JsonSerializer.Deserialize(content, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); user.Login.Should().Be(expectedUserName); user.Email.Should().Be(expectedEmail); @@ -62,16 +62,37 @@ public async Task StarredEndpoints_ShouldBehaveAsExpected(string org) string targetRepo = TestDataHelper.GenerateTestRepoName(); await CreateAppUsingDesigner(org, targetRepo); - using var putStarredResponse = await HttpClient.PutAsync($"designer/api/user/starred/{org}/{targetRepo}", null); + using var putStarredResponse = + await HttpClient.PutAsync($"designer/api/user/starred/{org}/{targetRepo}", null); putStarredResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); await GetAndVerifyStarredRepos(targetRepo); - using var deleteStarredResponse = await HttpClient.DeleteAsync($"designer/api/user/starred/{org}/{targetRepo}"); + using var deleteStarredResponse = + await HttpClient.DeleteAsync($"designer/api/user/starred/{org}/{targetRepo}"); deleteStarredResponse.StatusCode.Should().Be(HttpStatusCode.NoContent); await GetAndVerifyStarredRepos(); } + [Theory] + [InlineData(GiteaConstants.TestOrgUsername, true)] + [InlineData("OtherOrg", false)] + public async Task HasAccessToCreateRepository_ShouldReturnCorrectPermissions(string org, bool expectedCanCreate) + { + string requestUrl = $"designer/api/user/org-permissions/{org}"; + + using var response = await HttpClient.GetAsync(requestUrl); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + string content = await response.Content.ReadAsStringAsync(); + + var userRepositoryPermission = JsonSerializer.Deserialize(content, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + + userRepositoryPermission.Should().NotBeNull(); + userRepositoryPermission.CanCreateOrgRepo.Should().Be(expectedCanCreate); + } + private async Task GetAndVerifyStarredRepos(params string[] expectedStarredRepos) { using var response = await HttpClient.GetAsync("designer/api/user/starred"); @@ -83,6 +104,5 @@ private async Task GetAndVerifyStarredRepos(params string[] expectedStarredRepos content.Should().Contain(r => r.Name == expectedStarredRepo); } } - } } From 2f82f37f94003664e89666cce4bb7e4e4f9f127b Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Thu, 9 Jan 2025 19:11:33 +0100 Subject: [PATCH 05/15] and operator --- backend/src/Designer/Services/Implementation/UserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/Designer/Services/Implementation/UserService.cs b/backend/src/Designer/Services/Implementation/UserService.cs index 016a2989c6a..d1a2e25542b 100644 --- a/backend/src/Designer/Services/Implementation/UserService.cs +++ b/backend/src/Designer/Services/Implementation/UserService.cs @@ -39,6 +39,6 @@ private async Task HasPermissionToCreateOrgRepo(string org) private static bool CheckPermissionToCreateOrgRepo(Team team, string org) { - return team.can_create_org_repo & team.Organization.Username == org; + return team.can_create_org_repo && team.Organization.Username == org; } } From 907ab42e431c0667202926b51bd6d4b82a8694be Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Thu, 9 Jan 2025 19:18:06 +0100 Subject: [PATCH 06/15] improve naming --- backend/src/Designer/Controllers/UserController.cs | 4 ++-- .../{UserRepositoryPermission.cs => UserOrgPermission.cs} | 2 +- backend/src/Designer/Services/Implementation/UserService.cs | 4 ++-- backend/src/Designer/Services/Interfaces/IUserService.cs | 2 +- .../UserControllerGiteaIntegrationTests.cs | 6 +++--- backend/tests/Designer.Tests/Services/UserServiceTests.cs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) rename backend/src/Designer/Models/Dto/{UserRepositoryPermission.cs => UserOrgPermission.cs} (71%) diff --git a/backend/src/Designer/Controllers/UserController.cs b/backend/src/Designer/Controllers/UserController.cs index 888b2b3d6c8..e2d2582df07 100644 --- a/backend/src/Designer/Controllers/UserController.cs +++ b/backend/src/Designer/Controllers/UserController.cs @@ -87,8 +87,8 @@ public async Task PutStarred(string org, string repository) [Route("org-permissions/{org}")] public async Task HasAccessToCreateRepository(string org) { - UserRepositoryPermission userRepository = await _userService.GetUserRepositoryPermission(org); - return Ok(userRepository); + UserOrgPermission userOrg = await _userService.GetUserOrgPermission(org); + return Ok(userOrg); } /// diff --git a/backend/src/Designer/Models/Dto/UserRepositoryPermission.cs b/backend/src/Designer/Models/Dto/UserOrgPermission.cs similarity index 71% rename from backend/src/Designer/Models/Dto/UserRepositoryPermission.cs rename to backend/src/Designer/Models/Dto/UserOrgPermission.cs index b3ab9081b82..07096438d0d 100644 --- a/backend/src/Designer/Models/Dto/UserRepositoryPermission.cs +++ b/backend/src/Designer/Models/Dto/UserOrgPermission.cs @@ -1,6 +1,6 @@ namespace Altinn.Studio.Designer.Models.Dto; -public class UserRepositoryPermission +public class UserOrgPermission { public bool CanCreateOrgRepo { get; set; } } diff --git a/backend/src/Designer/Services/Implementation/UserService.cs b/backend/src/Designer/Services/Implementation/UserService.cs index d1a2e25542b..c0bd1bcde62 100644 --- a/backend/src/Designer/Services/Implementation/UserService.cs +++ b/backend/src/Designer/Services/Implementation/UserService.cs @@ -20,10 +20,10 @@ public UserService(IHttpContextAccessor httpContextAccessor, IGitea giteaApi) _giteaApi = giteaApi; } - public async Task GetUserRepositoryPermission(string org) + public async Task GetUserOrgPermission(string org) { bool canCreateOrgRepo = await HasPermissionToCreateOrgRepo(org); - return new UserRepositoryPermission() { CanCreateOrgRepo = canCreateOrgRepo }; + return new UserOrgPermission() { CanCreateOrgRepo = canCreateOrgRepo }; } private bool IsUserSelfOrg(string org) diff --git a/backend/src/Designer/Services/Interfaces/IUserService.cs b/backend/src/Designer/Services/Interfaces/IUserService.cs index 270fe89d8db..71fa1a657e2 100644 --- a/backend/src/Designer/Services/Interfaces/IUserService.cs +++ b/backend/src/Designer/Services/Interfaces/IUserService.cs @@ -5,5 +5,5 @@ namespace Altinn.Studio.Designer.Services.Interfaces; public interface IUserService { - public Task GetUserRepositoryPermission(string org); + public Task GetUserOrgPermission(string org); } diff --git a/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs b/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs index 86630fa5d3c..16fe78e9df9 100644 --- a/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs +++ b/backend/tests/Designer.Tests/GiteaIntegrationTests/UserControllerGiteaIntegrationTests.cs @@ -86,11 +86,11 @@ public async Task HasAccessToCreateRepository_ShouldReturnCorrectPermissions(str response.StatusCode.Should().Be(HttpStatusCode.OK); string content = await response.Content.ReadAsStringAsync(); - var userRepositoryPermission = JsonSerializer.Deserialize(content, + var userOrgPermission = JsonSerializer.Deserialize(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - userRepositoryPermission.Should().NotBeNull(); - userRepositoryPermission.CanCreateOrgRepo.Should().Be(expectedCanCreate); + userOrgPermission.Should().NotBeNull(); + userOrgPermission.CanCreateOrgRepo.Should().Be(expectedCanCreate); } private async Task GetAndVerifyStarredRepos(params string[] expectedStarredRepos) diff --git a/backend/tests/Designer.Tests/Services/UserServiceTests.cs b/backend/tests/Designer.Tests/Services/UserServiceTests.cs index 72ce750ed97..11526d8b5fa 100644 --- a/backend/tests/Designer.Tests/Services/UserServiceTests.cs +++ b/backend/tests/Designer.Tests/Services/UserServiceTests.cs @@ -25,7 +25,7 @@ public UserServiceTests() [Theory] [InlineData("org1", false)] [InlineData("org2", true)] - public async Task GetUserRepositoryPermission_ReturnsCorrectPermission(string org, bool expectedCanCreate) + public async Task GetUserOrgPermission_ReturnsCorrectPermission(string org, bool expectedCanCreate) { var teams = new List { @@ -40,7 +40,7 @@ public async Task GetUserRepositoryPermission_ReturnsCorrectPermission(string or var userService = new UserService(_httpContextAccessor.Object, _giteaApi.Object); - var result = await userService.GetUserRepositoryPermission(org); + var result = await userService.GetUserOrgPermission(org); Assert.NotNull(result); Assert.Equal(expectedCanCreate, result.CanCreateOrgRepo); From bc14a2f386992ef718b4522e61ae4b97df609683 Mon Sep 17 00:00:00 2001 From: "davidovrelid.com" Date: Thu, 9 Jan 2025 22:39:15 +0100 Subject: [PATCH 07/15] feat(dashboard): Improve error handling for missing permissions when creating repositories --- .../NewApplicationForm/NewApplicationForm.tsx | 20 +++++++++++++++---- .../ServiceOwnerSelector.tsx | 3 +++ .../queries/useUserOrgPermissionsQuery.ts | 20 +++++++++++++++++++ frontend/language/src/nb.json | 1 + frontend/packages/shared/src/api/paths.js | 3 +++ frontend/packages/shared/src/api/queries.ts | 2 ++ .../packages/shared/src/mocks/queriesMock.ts | 1 + .../packages/shared/src/types/QueryKey.ts | 1 + 8 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 frontend/dashboard/hooks/queries/useUserOrgPermissionsQuery.ts diff --git a/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.tsx b/frontend/dashboard/components/NewApplicationForm/NewApplicationForm.tsx index e3947330491..6a335bb0dcc 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 = + !userOrgPermission?.canCreateOrgRepo && !isFetching + ? t('dashboard.missing_service_owner_rights_error_message') + : ''; + + const hasCreateRepoAccessError = Boolean(createRepoAccessError); + return (
) : ( <> - + {submitButtonText} diff --git a/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx b/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx index 80f89c6c80f..bf6cabc0e2c 100644 --- a/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx +++ b/frontend/dashboard/components/ServiceOwnerSelector/ServiceOwnerSelector.tsx @@ -10,6 +10,7 @@ export type ServiceOwnerSelectorProps = { organizations: Organization[]; errorMessage?: string; name?: string; + onChange?: (org: string) => void; }; export const ServiceOwnerSelector = ({ @@ -18,6 +19,7 @@ export const ServiceOwnerSelector = ({ organizations, errorMessage, name, + onChange, }: ServiceOwnerSelectorProps) => { const { t } = useTranslation(); const serviceOwnerId: string = useId(); @@ -38,6 +40,7 @@ export const ServiceOwnerSelector = ({ name={name} id={serviceOwnerId} defaultValue={defaultValue} + onChange={(event) => onChange(event.target.value)} > {selectableOptions.map(({ value, label }) => (