Skip to content

Commit

Permalink
Merge branch 'main' into options-are-not-updated-in-certain-cases
Browse files Browse the repository at this point in the history
  • Loading branch information
standeren authored Jan 9, 2025
2 parents 56028f3 + 6a4ecf9 commit 48109b5
Show file tree
Hide file tree
Showing 49 changed files with 1,249 additions and 779 deletions.
6 changes: 3 additions & 3 deletions backend/packagegroups/NuGet.props
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
<PackageReference Update="Microsoft.DiaSymReader.Native" Version="1.7.0" />
<PackageReference Update="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" />
<PackageReference Update="Microsoft.FeatureManagement.AspNetCore" Version="3.5.0" />
<PackageReference Update="Scrutor" Version="4.2.2" />
<PackageReference Update="Microsoft.FeatureManagement.AspNetCore" Version="4.0.0" />
<PackageReference Update="Scrutor" Version="5.1.1" />
<PackageReference Update="Polly" Version="8.5.0" />
<PackageReference Update="Altinn.Authorization.ABAC" Version="0.0.8" />
<PackageReference Update="Altinn.ApiClients.Maskinporten" Version="9.2.0" />
Expand Down Expand Up @@ -66,7 +66,7 @@
<PackageReference Update="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Update="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageReference Update="Microsoft.CodeAnalysis.Common" Version="4.12.0" />
<PackageReference Update="Testcontainers" Version="3.10.0" />
<PackageReference Update="Testcontainers" Version="4.1.0" />
<PackageReference Update="Testcontainers.PostgreSql" Version="3.10.0" />
<PackageReference Update="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
<PackageReference Update="Microsoft.Extensions.DependencyModel" Version="9.0.0" />
Expand Down
2 changes: 1 addition & 1 deletion backend/src/Designer/Models/LayoutSets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Altinn.Studio.Designer.Models;
public class LayoutSets : Altinn.App.Core.Models.LayoutSets
{
[JsonPropertyName("$schema")]
public required string Schema { get; set; }
public string? Schema { get; set; }

[JsonPropertyName("sets")]
public new required List<LayoutSetConfig> Sets { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Altinn.App.Core.Models.Validation;
Expand Down Expand Up @@ -75,7 +76,7 @@ public static ValidationIssueWithSource FromIssue(ValidationIssue issue, string
public required string Source { get; set; }

/// <summary>
/// Weather the issue is from a validator that correctly implements <see cref="IValidator.HasRelevantChanges"/>.
/// Weather the issue is from a validator that correctly implements IValidator.HasRelevantChanges from AppCoreLib/>.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[JsonPropertyName("noIncrementalUpdates")]
Expand Down Expand Up @@ -104,7 +105,7 @@ public static ValidationIssueWithSource FromIssue(ValidationIssue issue, string
/// <summary>
/// API responses that returns validation issues grouped by source, typically return a list of these.
/// </summary>
/// <param name="Source">The <see cref="IValidator.ValidationSource"/> for the Validator that created theese issues</param>
/// <param name="Source">The source (of type IValidator.ValidationSource in AppCoreLib) for the Validator that created these issues</param>
/// <param name="Issues">List of issues</param>
public record ValidationSourcePair(
[property: JsonPropertyName("source")] string Source,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
Expand Down Expand Up @@ -51,14 +52,15 @@ public async Task<List<Option>> GetOptionsList(string org, string repo, string d
cancellationToken.ThrowIfCancellationRequested();
var altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, repo, developer);

string optionsListString = await altinnAppGitRepository.GetOptionsList(optionsListId, cancellationToken);
var optionsList = JsonSerializer.Deserialize<List<Option>>(optionsListString);
List<Option> optionsList;

string optionsListString = await altinnAppGitRepository.GetOptionsList(optionsListId, cancellationToken);
try
{
optionsList = JsonSerializer.Deserialize<List<Option>>(optionsListString);
optionsList.ForEach(ValidateOption);
}
catch (ValidationException)
catch (Exception ex) when (ex is ValidationException || ex is JsonException)
{
throw new InvalidOptionsFormatException($"One or more of the options have an invalid format in option list: {optionsListId}.");
}
Expand Down
12 changes: 5 additions & 7 deletions backend/src/Designer/Services/Implementation/PreviewService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,16 @@ namespace Altinn.Studio.Designer.Services.Implementation;
/// Constructor
/// </remarks>
/// <param name="altinnGitRepositoryFactory">IAltinnGitRepository</param>
/// <param name="dataService"></param>
public class PreviewService(IAltinnGitRepositoryFactory altinnGitRepositoryFactory) : IPreviewService
{
private readonly IAltinnGitRepositoryFactory _altinnGitRepositoryFactory = altinnGitRepositoryFactory;
public const string MockDataModelIdPrefix = "MockDataModel";
public const string MockDataTaskId = "test-datatask-id";

/// <inheritdoc />
public async Task<Instance> GetMockInstance(string org, string app, string developer, int? instanceOwnerPartyId, string layoutSetName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
Application applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(cancellationToken);
string task = await GetTaskForLayoutSetName(org, app, developer, layoutSetName, cancellationToken);
bool shouldProcessActAsReceipt = task == Constants.General.CustomReceiptId;
Expand Down Expand Up @@ -69,7 +67,7 @@ public async Task<Instance> GetMockInstance(string org, string app, string devel
public async Task<DataType> GetDataTypeForLayoutSetName(string org, string app, string developer, string layoutSetName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
string task = await GetTaskForLayoutSetName(org, app, developer, layoutSetName, cancellationToken);
Application applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(cancellationToken);
if (applicationMetadata.DataTypes is { Count: > 0 })
Expand All @@ -84,7 +82,7 @@ public async Task<DataType> GetDataTypeForLayoutSetName(string org, string app,
public async Task<List<string>> GetTasksForAllLayoutSets(string org, string app, string developer, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
try
{
LayoutSets layoutSets = await altinnAppGitRepository.GetLayoutSetsFile(cancellationToken);
Expand Down Expand Up @@ -115,7 +113,7 @@ public async Task<List<string>> GetTasksForAllLayoutSets(string org, string app,
public async Task<string> GetTaskForLayoutSetName(string org, string app, string developer, string layoutSetName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
string task = "Task_1";
if (!string.IsNullOrEmpty(layoutSetName))
{
Expand All @@ -127,7 +125,7 @@ public async Task<string> GetTaskForLayoutSetName(string org, string app, string

private async Task<List<DataElement>> GetDataTypesForInstance(string org, string app, string developer, string layoutSetName, bool shouldProcessActAsReceipt)
{
AltinnAppGitRepository altinnAppGitRepository = _altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
AltinnAppGitRepository altinnAppGitRepository = altinnGitRepositoryFactory.GetAltinnAppGitRepository(org, app, developer);
DataType dataType = await GetDataTypeForLayoutSetName(org, app, developer, layoutSetName);
string dataTypeForDataElement = shouldProcessActAsReceipt ? await GetDataTypeForCustomReceipt(altinnAppGitRepository) : dataType?.Id;
ApplicationMetadata applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Altinn.Studio.Designer.Filters;
using Altinn.Studio.Designer.Models;
using Altinn.Studio.Designer.Models.Dto;
using Designer.Tests.Controllers.ApiTests;
using Designer.Tests.Utils;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -63,8 +65,24 @@ public async Task GetOptionLists_Returns200OK_WithOptionListsData()
{
// Arrange
const string repo = "app-with-options";
string apiUrl = $"/designer/api/ttd/{repo}/options/option-lists";
string targetRepository = TestDataHelper.GenerateTestRepoName();
await CopyRepositoryForTest("ttd", repo, "testUser", targetRepository);
string apiUrl = $"/designer/api/ttd/{targetRepository}/options/option-lists";
using HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, apiUrl);
string optionListMissingValue = @"[{ ""label"": ""someLabel""}]";
string optionListMissingLabel = @"[{ ""value"": ""someValue""}]";
string optionListTrailingComma = @"[{ ""value"": ""someValue"", ""label"": ""someLabel"",}]";
string optionListLabelWithObject = @"[{ ""value"": ""someValue"", ""label"": {}}]";
string optionListLabelWithNumber = @"[{ ""value"": ""someValue"", ""label"": 12345}]";
string optionListLabelWithBool = @"[{ ""value"": ""someValue"", ""label"": true}]";
string repoPath = TestDataHelper.GetTestDataRepositoryDirectory("ttd", targetRepository, "testUser");
string filePath = Path.Combine(repoPath, "App/options");
await File.WriteAllTextAsync(Path.Combine(filePath, "optionListMissingValue.json"), optionListMissingValue);
await File.WriteAllTextAsync(Path.Combine(filePath, "optionListMissingLabel.json"), optionListMissingLabel);
await File.WriteAllTextAsync(Path.Combine(filePath, "optionListTrailingComma.json"), optionListTrailingComma);
await File.WriteAllTextAsync(Path.Combine(filePath, "optionListLabelWithObject.json"), optionListLabelWithObject);
await File.WriteAllTextAsync(Path.Combine(filePath, "optionListLabelWithNumber.json"), optionListLabelWithNumber);
await File.WriteAllTextAsync(Path.Combine(filePath, "optionListLabelWithBool.json"), optionListLabelWithBool);

// Act
using HttpResponseMessage response = await HttpClient.SendAsync(httpRequestMessage);
Expand All @@ -77,7 +95,13 @@ public async Task GetOptionLists_Returns200OK_WithOptionListsData()
{
new () { Title = "options-with-null-fields", Data = null, HasError = true },
new () { Title = "other-options", HasError = false },
new () { Title = "test-options", HasError = false }
new () { Title = "test-options", HasError = false },
new () { Title = "optionListMissingValue", Data = null, HasError = true },
new () { Title = "optionListMissingLabel", Data = null, HasError = true },
new () { Title = "optionListTrailingComma", Data = null, HasError = true },
new () { Title = "optionListLabelWithObject", Data = null, HasError = true },
new () { Title = "optionListLabelWithNumber", Data = null, HasError = true },
new () { Title = "optionListLabelWithBool", Data = null, HasError = true }
}, options => options.Excluding(x => x.Data));
}

Expand Down
11 changes: 8 additions & 3 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1453,7 +1453,7 @@
"ux_editor.component_properties.target": "Hva vil du vise i oppsummeringen?",
"ux_editor.component_properties.target_description": "Her kan du velge hva som skal vises på oppsummeringssiden. Du kan for eksempel vise hele sidegrupper, utvalgte sider eller utvalgte komponenter",
"ux_editor.component_properties.target_invalid": "Ugyldig mål",
"ux_editor.component_properties.target_taskId": "1. Oppsummer fra denne sidegruppen",
"ux_editor.component_properties.target_layoutSet_id": "1. Oppsummer fra denne sidegruppen",
"ux_editor.component_properties.target_type": "2. Vis sidegruppe, side eller komponent",
"ux_editor.component_properties.target_unit_component": "3. Komponent",
"ux_editor.component_properties.target_unit_layout_set": "3. Sidegruppe",
Expand Down Expand Up @@ -1598,7 +1598,7 @@
"ux_editor.modal_new_option": "Legg til alternativ",
"ux_editor.modal_properties_add_radio_button_options": "Hvordan vil du legge til radioknapper?",
"ux_editor.modal_properties_code_list": "Velg fra biblioteket",
"ux_editor.modal_properties_code_list_alert_title": "Du redigerer nå en kodeliste i biblioteket.",
"ux_editor.modal_properties_code_list_alert_title": "Du er i ferd med å endre en kodeliste i biblioteket. Da endrer du også kodelisten alle andre steder der den blir brukt.",
"ux_editor.modal_properties_code_list_button_title_library": "Kodeliste fra biblioteket",
"ux_editor.modal_properties_code_list_button_title_manual": "Kodeliste på komponenten",
"ux_editor.modal_properties_code_list_custom_list": "Egendefinert kodeliste",
Expand Down Expand Up @@ -1756,13 +1756,17 @@
"ux_editor.options.code_list_only": "Denne komponenten støtter bare oppsett med forhåndsdefinerte kodelister.",
"ux_editor.options.code_list_reference_id.description": "Her kan du legge til en referanse-ID til en dynamisk kodeliste som er satt opp i koden.",
"ux_editor.options.code_list_reference_id.description_details": "Du bruker dynamiske kodelister for å tilpasse alternativer for brukerne. Det kan for eksempel være tilpasninger ut fra geografisk plassering, eller valg brukeren gjør tidligere i skjemaet.",
"ux_editor.options.modal_header_library_code_list": "Kodeliste fra biblioteket",
"ux_editor.options.modal_header_manual_code_list": "Kodeliste på komponenten",
"ux_editor.options.modal_header_select_library_code_list": "Biblioteket for kodelister",
"ux_editor.options.multiple": "{{value}} alternativer",
"ux_editor.options.option_edit_text": "Rediger kodeliste",
"ux_editor.options.option_remove_text": "Fjern valgt kodeliste",
"ux_editor.options.option_remove_text": "Fjern kobling til kodelisten",
"ux_editor.options.section_heading": "Valg for kodelister",
"ux_editor.options.single": "{{value}} alternativ",
"ux_editor.options.tab_code_list": "Velg kodeliste",
"ux_editor.options.tab_manual": "Sett opp egne alternativer",
"ux_editor.options.tab_option_list_alert_title": "Komponenten refererer allerede til en egendefinert ID. Legger du til en ny kodeliste her vil du overstyre den egendefinerte ID-en.",
"ux_editor.options.tab_reference_id": "Angi referanse-ID",
"ux_editor.options.tab_reference_id_alert_title": "Du har allerede referert til en kodeliste. Skriver du inn en ID, vil referansen bli slettet.",
"ux_editor.options.upload_title": "Last opp egen kodeliste",
Expand Down Expand Up @@ -1827,6 +1831,7 @@
"ux_editor.properties_panel.texts.no_properties": "Det er ingen tekster å konfigurere for denne komponenten.",
"ux_editor.properties_panel.texts.sub_title_images": "Valg for bilde",
"ux_editor.properties_panel.texts.sub_title_texts": "Tekster",
"ux_editor.radios_error_DuplicateValues": "Alle verdier må være unike.",
"ux_editor.radios_error_NoOptions": "Det må være minst én radioknapp.",
"ux_editor.radios_option": "Radioknapp {{optionNumber}}",
"ux_editor.search_text_resources_close": "Lukk tekstsøk",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type StudioDecimalInputProps = Override<
description?: string;
onChange: (value: number) => void;
value?: number;
validationErrorMessage: string;
validationErrorMessage?: string;
},
StudioTextfieldProps
>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.textfieldCell,
.textareaCell,
.textResourceCell {
.textResourceCell,
.numberCell {
padding-block: var(--fds-spacing-1);
padding-inline-end: var(--fds-spacing-2);
padding-inline-start: 0;
Expand All @@ -17,6 +18,7 @@

:is(
.textfieldCell:not(:hover) input,
.numberfieldCell:not(:hover) input,
.textareaCell:not(:hover) textarea,
.textResourceCell:not(:hover) input
):not(:hover):not(:active):not(:focus),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { StudioTable } from '../../StudioTable';
import type { FocusEvent, ForwardedRef, ReactElement } from 'react';
import React, { useCallback } from 'react';
import classes from './Cell.module.css';
import { BaseInputCell } from './BaseInputCell';
import cn from 'classnames';
import { isCaretAtEnd, isCaretAtStart, isSomethingSelected } from '../dom-utils/caretUtils';
import type { StudioDecimalInputProps } from '../../StudioDecimalInput';
import { StudioDecimalInput } from '../../StudioDecimalInput';
import { useEventProps } from './useEventProps';

export type CellNumberfieldProps = StudioDecimalInputProps;

export class CellNumberfield extends BaseInputCell<HTMLInputElement, CellNumberfieldProps> {
render(
{ className: givenClass, onFocus, ...rest }: CellNumberfieldProps,
ref: ForwardedRef<HTMLInputElement>,
): ReactElement {
/* eslint-disable react-hooks/rules-of-hooks */
/* Eslint misinterprets this as a class component, while it's really just a functional component within a class */

const handleFocus = useCallback(
(event: FocusEvent<HTMLInputElement>): void => {
onFocus?.(event);
event.currentTarget.select();
},
[onFocus],
);

const eventProps = useEventProps<FocusEvent, FocusEvent, number>({
onFocus: handleFocus,
...rest,
});

const className = cn(classes.numberfieldCell, givenClass);

return (
<StudioTable.Cell className={className}>
<StudioDecimalInput hideLabel ref={ref} size='small' {...rest} {...eventProps} />
</StudioTable.Cell>
);
}

shouldMoveFocusOnArrowKey({ key, currentTarget }) {
if (isSomethingSelected(currentTarget)) return false;
switch (key) {
case 'ArrowUp':
return isCaretAtStart(currentTarget);
case 'ArrowDown':
return isCaretAtEnd(currentTarget);
case 'ArrowLeft':
return isCaretAtStart(currentTarget);
case 'ArrowRight':
return isCaretAtEnd(currentTarget);
}
}

shouldMoveFocusOnEnterKey = () => true;
}
Loading

0 comments on commit 48109b5

Please sign in to comment.