Skip to content

Commit 4db05cc

Browse files
authored
Merge branch 'main' into clean-up-studio-toggleable-textfield
2 parents 1ea3bf3 + 9b7bba9 commit 4db05cc

File tree

86 files changed

+1212
-309
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+1212
-309
lines changed

backend/src/Designer/Controllers/AppDevelopmentController.cs

+5-8
Original file line numberDiff line numberDiff line change
@@ -547,17 +547,14 @@ public ActionResult GetWidgetSettings(string org, string app)
547547
[Route("option-list-ids")]
548548
public ActionResult GetOptionListIds(string org, string app)
549549
{
550-
try
551-
{
552-
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
553-
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
554-
string[] optionListIds = altinnAppGitRepository.GetOptionsListIds();
555-
return Ok(optionListIds);
556-
}
557-
catch (LibGit2Sharp.NotFoundException)
550+
string developer = AuthenticationHelper.GetDeveloperUserName(HttpContext);
551+
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
552+
string[] optionListIds = altinnAppGitRepository.GetOptionsListIds();
553+
if (optionListIds.Length == 0)
558554
{
559555
return NoContent();
560556
}
557+
return Ok(optionListIds);
561558
}
562559

563560
[HttpGet("app-version")]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Altinn.Studio.Designer.Helpers;
4+
using Altinn.Studio.Designer.Services.Interfaces;
5+
using Microsoft.AspNetCore.Authorization;
6+
using Microsoft.AspNetCore.Mvc;
7+
8+
namespace Altinn.Studio.Designer.Controllers
9+
{
10+
[Route("designer/api/[controller]")]
11+
[ApiController]
12+
public class ContactController : ControllerBase
13+
{
14+
private readonly IGitea _giteaService;
15+
16+
public ContactController(IGitea giteaService)
17+
{
18+
_giteaService = giteaService;
19+
}
20+
21+
[AllowAnonymous]
22+
[HttpGet("belongs-to-org")]
23+
public async Task<IActionResult> BelongsToOrg()
24+
{
25+
bool isNotAuthenticated = !AuthenticationHelper.IsAuthenticated(HttpContext);
26+
if (isNotAuthenticated)
27+
{
28+
return Ok(new BelongsToOrgDto { BelongsToOrg = false });
29+
}
30+
31+
try
32+
{
33+
var organizations = await _giteaService.GetUserOrganizations();
34+
return Ok(new BelongsToOrgDto { BelongsToOrg = organizations.Count > 0 });
35+
}
36+
catch (Exception)
37+
{
38+
return Ok(new BelongsToOrgDto { BelongsToOrg = false });
39+
}
40+
}
41+
}
42+
}

backend/src/Designer/Helpers/AuthenticationHelper.cs

+5
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,10 @@ public static Task<string> GetDeveloperAppTokenAsync(this HttpContext context)
2323
{
2424
return context.GetTokenAsync("access_token");
2525
}
26+
27+
public static bool IsAuthenticated(HttpContext context)
28+
{
29+
return context.User.Identity?.IsAuthenticated ?? false;
30+
}
2631
}
2732
}

backend/src/Designer/Infrastructure/GitRepository/AltinnAppGitRepository.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,7 @@ public string[] GetOptionsListIds()
798798
string optionsFolder = Path.Combine(OptionsFolderPath);
799799
if (!DirectoryExistsByRelativePath(optionsFolder))
800800
{
801-
throw new NotFoundException("Options folder not found.");
801+
return [];
802802
}
803803

804804
string[] fileNames = GetFilesByRelativeDirectoryAscSorted(optionsFolder, "*.json");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
using System.Text.Json.Serialization;
2+
3+
public class BelongsToOrgDto
4+
{
5+
[JsonPropertyName("belongsToOrg")]
6+
public bool BelongsToOrg { get; set; }
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Net;
2+
using System.Net.Http;
3+
using System.Threading.Tasks;
4+
using Designer.Tests.Controllers.ApiTests;
5+
using Microsoft.AspNetCore.Authentication;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.Mvc.Testing;
8+
using Microsoft.AspNetCore.TestHost;
9+
using Microsoft.Extensions.Configuration;
10+
using Microsoft.Extensions.DependencyInjection;
11+
using Xunit;
12+
13+
namespace Designer.Tests.Controllers.ContactController;
14+
15+
public class FetchBelongsToOrgTests : DesignerEndpointsTestsBase<FetchBelongsToOrgTests>,
16+
IClassFixture<WebApplicationFactory<Program>>
17+
{
18+
public FetchBelongsToOrgTests(WebApplicationFactory<Program> factory) : base(factory)
19+
{
20+
}
21+
22+
[Fact]
23+
public async Task UsersThatBelongsToOrg_ShouldReturn_True()
24+
{
25+
string url = "/designer/api/contact/belongs-to-org";
26+
27+
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
28+
29+
var response = await HttpClient.SendAsync(httpRequestMessage);
30+
var responseContent = await response.Content.ReadAsAsync<BelongsToOrgDto>();
31+
32+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
33+
Assert.True(responseContent.BelongsToOrg);
34+
}
35+
36+
[Fact]
37+
public async Task UsersThatDoNotBelongsToOrg_ShouldReturn_False_IfAnonymousUser()
38+
{
39+
string configPath = GetConfigPath();
40+
IConfiguration configuration = new ConfigurationBuilder()
41+
.AddJsonFile(configPath, false, false)
42+
.AddJsonStream(GenerateJsonOverrideConfig())
43+
.AddEnvironmentVariables()
44+
.Build();
45+
46+
var anonymousClient = Factory.WithWebHostBuilder(builder =>
47+
{
48+
builder.UseConfiguration(configuration);
49+
builder.ConfigureTestServices(services =>
50+
{
51+
services.AddAuthentication("Anonymous")
52+
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Anonymous", options => { });
53+
});
54+
}).CreateDefaultClient();
55+
56+
string url = "/designer/api/contact/belongs-to-org";
57+
58+
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url);
59+
60+
var response = await anonymousClient.SendAsync(httpRequestMessage);
61+
var responseContent = await response.Content.ReadAsAsync<BelongsToOrgDto>();
62+
63+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
64+
Assert.False(responseContent.BelongsToOrg);
65+
}
66+
}

backend/tests/Designer.Tests/Controllers/OptionsController/GetOptionListsReferencesTests.cs

+24
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System.Collections.Generic;
2+
using System.IO;
23
using System.Net.Http;
34
using System.Text.Json;
45
using System.Threading.Tasks;
56
using Altinn.Studio.Designer.Models.Dto;
67
using Designer.Tests.Controllers.ApiTests;
8+
using Designer.Tests.Utils;
79
using Microsoft.AspNetCore.Http;
810
using Microsoft.AspNetCore.Mvc.Testing;
911
using Xunit;
@@ -14,6 +16,7 @@ public class GetOptionListsReferencesTests : DesignerEndpointsTestsBase<GetOptio
1416
{
1517
const string RepoWithUsedOptions = "app-with-options";
1618
const string RepoWithUnusedOptions = "app-with-layoutsets";
19+
const string RepoWithoutOptions = "empty-app";
1720

1821
public GetOptionListsReferencesTests(WebApplicationFactory<Program> factory) : base(factory)
1922
{
@@ -85,4 +88,25 @@ public async Task GetOptionListsReferences_Returns200Ok_WithEmptyOptionsReferenc
8588
Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode);
8689
Assert.Equivalent("[]", responseBody);
8790
}
91+
92+
[Fact]
93+
public async Task GetOptionListsReferences_Returns200Ok_WithEmptyOptionsReferences_WhenAppDoesNotHaveOptions()
94+
{
95+
string targetRepository = TestDataHelper.GenerateTestRepoName();
96+
await CopyRepositoryForTest("ttd", RepoWithoutOptions, "testUser", targetRepository);
97+
string repoPath = TestDataHelper.GetTestDataRepositoryDirectory("ttd", targetRepository, "testUser");
98+
string exampleLayout = @"{ ""data"": {""layout"": []}}";
99+
string filePath = Path.Combine(repoPath, "App/ui/form/layouts");
100+
Directory.CreateDirectory(filePath);
101+
await File.WriteAllTextAsync(Path.Combine(filePath, "exampleLayout.json"), exampleLayout);
102+
103+
string apiUrl = $"/designer/api/ttd/{targetRepository}/options/usage";
104+
using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, apiUrl);
105+
106+
using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage);
107+
string responseBody = await response.Content.ReadAsStringAsync();
108+
109+
Assert.Equal(StatusCodes.Status200OK, (int)response.StatusCode);
110+
Assert.Equivalent("[]", responseBody);
111+
}
88112
}

backend/tests/Designer.Tests/Infrastructure/GitRepository/AltinnAppGitRepositoryTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,14 @@ public Task GetOptionListIds_WithAppThatHasOptionLists_ShouldReturnOptionListPat
296296
}
297297

298298
[Fact]
299-
public Task GetOptionListIds_WithAppThatHasNoOptionLists_ShouldThrowNotFoundException()
299+
public void GetOptionListIds_WithAppThatHasNoOptionLists_ShouldReturnEmptyList()
300300
{
301301
string org = "ttd";
302302
string repository = "empty-app";
303303
string developer = "testUser";
304304
AltinnAppGitRepository altinnAppGitRepository = PrepareRepositoryForTest(org, repository, developer);
305-
Assert.Throws<LibGit2Sharp.NotFoundException>(altinnAppGitRepository.GetOptionsListIds);
306-
return Task.CompletedTask;
305+
var optionsIds = altinnAppGitRepository.GetOptionsListIds();
306+
Assert.Equal([], optionsIds);
307307
}
308308

309309
[Fact]

backend/tests/Designer.Tests/Mocks/IGiteaMock.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Altinn.Studio.Designer.Services.Interfaces;
1010

1111
using Designer.Tests.Utils;
12+
using Organization = Altinn.Studio.Designer.RepositoryClient.Model.Organization;
1213

1314
namespace Designer.Tests.Mocks
1415
{
@@ -131,7 +132,13 @@ public Task<List<Team>> GetTeams()
131132

132133
public Task<List<Organization>> GetUserOrganizations()
133134
{
134-
throw new NotImplementedException();
135+
var organizations = new List<Organization>
136+
{
137+
new Organization { Username = "Org1", Id = 1 }, // Example items
138+
new Organization { Username = "Org2", Id = 2 }
139+
};
140+
141+
return Task.FromResult(organizations);
135142
}
136143

137144
public Task<IList<Repository>> GetUserRepos()

frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
useAddOptionListMutation,
1515
useUpdateOptionListMutation,
1616
useUpdateOptionListIdMutation,
17+
useDeleteOptionListMutation,
1718
} from 'app-shared/hooks/mutations';
1819
import { mapToCodeListsUsage } from './utils/mapToCodeListsUsage';
1920

@@ -24,6 +25,7 @@ export function AppContentLibrary(): React.ReactElement {
2425
org,
2526
app,
2627
);
28+
const { mutate: deleteOptionList } = useDeleteOptionListMutation(org, app);
2729
const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, {
2830
hideDefaultError: (error: AxiosError<ApiError>) => isErrorUnknown(error),
2931
});
@@ -65,6 +67,7 @@ export function AppContentLibrary(): React.ReactElement {
6567
codeList: {
6668
props: {
6769
codeListsData,
70+
onDeleteCodeList: deleteOptionList,
6871
onUpdateCodeListId: handleUpdateCodeListId,
6972
onUpdateCodeList: handleUpdate,
7073
onUploadCodeList: handleUpload,

frontend/app-development/features/overview/components/News/NewsContent/news.nb.json

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
{
22
"$schema": "news.schema.json",
33
"news": [
4+
{
5+
"date": "2025-01-17",
6+
"title": "Nå kan du ha flere datamodeller i et prosess-steg!",
7+
"content": "Du kan nå koble komponenter i et prosess-steg til andre datamodeller, ikke bare til den overordnede datamodellen for steget. Dette gir deg nye muligheter til å håndtere data i appene dine."
8+
},
49
{
510
"date": "2024-11-29",
611
"title": "Prøv nytt design på Utforming!",

frontend/language/src/nb.json

+9
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@
1212
"api_errors.ResourceNotFound": "Studio prøver å finne en fil som ikke finnes.",
1313
"api_errors.Unauthorized": "Handlingen du prøver å utføre krever rettigheter du ikke har. Du blir nå logget ut.",
1414
"api_errors.UploadedImageNotValid": "Det opplastede bildet er ikke en gyldig filtype",
15+
"app_content_library.code_lists.clear_search_button_label": "Fjern søkeord",
1516
"app_content_library.code_lists.code_list_accordion_title": "Kodelistenavn: {{codeListTitle}}",
1617
"app_content_library.code_lists.code_list_accordion_usage_sub_title_plural": "Kodelisten brukes i {{codeListUsagesCount}} komponenter.",
1718
"app_content_library.code_lists.code_list_accordion_usage_sub_title_single": "Kodelisten brukes i {{codeListUsagesCount}} komponent.",
19+
"app_content_library.code_lists.code_list_delete": "Slett kodeliste",
20+
"app_content_library.code_lists.code_list_delete_disabled_title": "Før du kan å slette kodelisten, må du fjerne den fra der den er brukt i appen.",
21+
"app_content_library.code_lists.code_list_delete_enabled_title": "Slett kodelisten fra biblioteket.",
1822
"app_content_library.code_lists.code_list_edit_id_label": "Navn på kodeliste",
1923
"app_content_library.code_lists.code_list_edit_id_title": "Rediger navn på kodelisten {{codeListName}}",
2024
"app_content_library.code_lists.code_list_show_usage": "Se hvor kodelisten er tatt i bruk",
@@ -153,11 +157,16 @@
153157
"code_list_editor.text_resource.label.select": "Finn ledetekst for verdi nummer {{number}}",
154158
"code_list_editor.text_resource.label.value": "Oppgi ledetekst for verdi nummer {{number}}",
155159
"code_list_editor.value_item": "Verdi for alternativ {{number}}",
160+
"contact.altinn_servicedesk.content": "Er du tjenesteeier og har du behov for hjelp? Ta kontakt med oss!",
161+
"contact.altinn_servicedesk.heading": "Altinn Servicedesk",
156162
"contact.email.content": "Du kan skrive en e-post til Altinn servicedesk hvis du har spørsmål om å opprette organisasjoner eller miljøer, opplever tekniske problemer eller har spørsmål om dokumentasjonen eller andre ting.",
157163
"contact.email.heading": "Send e-post",
158164
"contact.github_issue.content": "Hvis du har behov for funksjonalitet eller ser feil og mangler i Studio som vi må fikse, kan du opprette en sak i Github, så ser vi på den.",
159165
"contact.github_issue.heading": "Rapporter feil og mangler til oss",
160166
"contact.github_issue.link_label": "Opprett sak i Github",
167+
"contact.serviceDesk.email": "<b>E-post:</b> <a>[email protected]</a>",
168+
"contact.serviceDesk.emergencyPhone": "<b>Vakttelefon:</b> <a>94 49 00 02</a> (tilgjengelig kl. 15:45–07:00)",
169+
"contact.serviceDesk.phone": "<b>Telefon:</b> <a>75 00 62 99</a>",
161170
"contact.slack.content": "Hvis du har spørsmål om hvordan du bygger en app, kan du snakke direkte med utviklingsteamet i Altinn Studio på Slack. De hjelper deg med å",
162171
"contact.slack.content_list": "<0>bygge appene slik du ønsker</0><0>svare på spørsmål og veilede deg</0><0>ta imot innspill på ny funksjonalitet</0>",
163172
"contact.slack.heading": "Skriv melding til oss Slack",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.studioSearch div:empty {
2+
display: contents;
3+
}

frontend/libs/studio-components/src/components/StudioSearch/StudioSearch.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { forwardRef, useId } from 'react';
22
import { Label, Search, type SearchProps } from '@digdir/designsystemet-react';
33
import type { WithoutAsChild } from '../../types/WithoutAsChild';
4+
import classes from './StudioSearch.module.css';
45

56
export type StudioSearchProps = WithoutAsChild<SearchProps>;
67

@@ -10,14 +11,15 @@ const StudioSearch = forwardRef<HTMLInputElement, StudioSearchProps>(
1011
const searchId = id ?? generatedId;
1112
const showLabel = !!label;
1213

14+
// TODO: Remove studioSearch css class when Design System is updated. See issue: https://github.com/digdir/designsystemet/issues/2765
1315
return (
1416
<div className={className}>
1517
{showLabel && (
1618
<Label htmlFor={searchId} spacing>
1719
{label}
1820
</Label>
1921
)}
20-
<Search {...rest} id={searchId} size={size} ref={ref} />
22+
<Search {...rest} className={classes.studioSearch} id={searchId} size={size} ref={ref} />
2123
</div>
2224
);
2325
},

frontend/libs/studio-content-library/mocks/mockPagesConfig.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const mockPagesConfig: PagesConfig = {
1212
codeList: {
1313
props: {
1414
codeListsData: codeListsDataMock,
15+
onDeleteCodeList: () => {},
1516
onUpdateCodeListId: () => {},
1617
onUpdateCodeList: () => {},
1718
onUploadCodeList: () => {},

0 commit comments

Comments
 (0)