Skip to content
2 changes: 1 addition & 1 deletion src/Altinn.App.Core/Features/IInstanceDataAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public interface IInstanceDataAccessor
/// <see cref="IInstanceDataAccessor"/> directly in the expression evaluator.
/// </summary>
/// <returns></returns>
internal LayoutEvaluatorState? GetLayoutEvaluatorState();
internal LayoutEvaluatorState GetLayoutEvaluatorState();

/// <summary>
/// <para>Set the authentication method used when reading and writing data of the given data type.</para>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public DataElement GetDataElement(DataElementIdentifier dataElementIdentifier)
return _dataAccessor.GetDataElement(dataElementIdentifier);
}

public LayoutEvaluatorState? GetLayoutEvaluatorState()
public LayoutEvaluatorState GetLayoutEvaluatorState()
{
throw new NotImplementedException(
"GetLayoutEvaluatorState is not implemented in CleanInstanceDataAccessor, because LayoutEvaluatorState will be deprecated."
Expand Down
8 changes: 2 additions & 6 deletions src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,19 @@
private static readonly StorageAuthenticationMethod _defaultAuthenticationMethod =
StorageAuthenticationMethod.CurrentUser();

public InstanceDataUnitOfWork(
Instance instance,
IDataClient dataClient,
IInstanceClient instanceClient,
ApplicationMetadata appMetadata,
ITranslationService translationService,
ModelSerializationService modelSerializationService,
IAppResources appResources,
IOptions<FrontEndSettings> frontEndSettings,
string? taskId,
string? language,
Telemetry? telemetry = null
)

Check warning on line 79 in src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Constructor has 11 parameters, which is greater than the 7 authorized.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoEqx0LMOhZibJAA&open=AZ5pGoEqx0LMOhZibJAA&pullRequest=1753
{
if (instance.Id is not null)
{
Expand Down Expand Up @@ -181,19 +181,15 @@

private LayoutEvaluatorState? _layoutEvaluatorStateCache;

public LayoutEvaluatorState? GetLayoutEvaluatorState()
public LayoutEvaluatorState GetLayoutEvaluatorState()
{
if (TaskId is null)
{
return null;
}
if (_layoutEvaluatorStateCache is not null)
{
return _layoutEvaluatorStateCache;
}

// Could use a double lock here, but a deadlock is more problematic than creating the state twice
var layouts = _appResources.GetLayoutModelForTask(TaskId);
var layouts = TaskId is null ? null : _appResources.GetLayoutModelForTask(TaskId);

_layoutEvaluatorStateCache = new LayoutEvaluatorState(
this,
Expand Down Expand Up @@ -415,7 +411,7 @@
}
}

public DataElementChanges GetDataElementChanges(bool initializeAltinnRowId)

Check failure on line 414 in src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 23 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoEqx0LMOhZibJAB&open=AZ5pGoEqx0LMOhZibJAB&pullRequest=1753
{
if (HasAbandonIssues)
{
Expand Down Expand Up @@ -629,7 +625,7 @@
}

// Delete data elements
foreach (var change in changes.AllChanges.Where(c => c.Type == ChangeType.Deleted))

Check warning on line 628 in src/Altinn.App.Core/Internal/Data/InstanceDataUnitOfWork.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Loop should be simplified by calling Select(change => change.DataElementIdentifier))

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoEqx0LMOhZibJAC&open=AZ5pGoEqx0LMOhZibJAC&pullRequest=1753
{
async Task DeleteData()
{
Expand Down
6 changes: 3 additions & 3 deletions src/Altinn.App.Core/Internal/Data/PreviousDataAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal class PreviousDataAccessor : IInstanceDataAccessor
private readonly FrontEndSettings _frontEndSettings;
private readonly ITranslationService _translationService;
private readonly Telemetry? _telemetry;
private readonly Lazy<LayoutEvaluatorState?> _layoutEvaluatorState;
private readonly Lazy<LayoutEvaluatorState> _layoutEvaluatorState;

private readonly ConcurrentDictionary<DataElementIdentifier, Task<IFormDataWrapper>> _previousDataCache = new();

Expand All @@ -41,7 +41,7 @@ public PreviousDataAccessor(
_layoutEvaluatorState = new(() =>
{
var originalState = _dataAccessor.GetLayoutEvaluatorState();
return originalState?.WithDataAccessor(this);
return originalState.WithDataAccessor(this);
});
}

Expand Down Expand Up @@ -106,7 +106,7 @@ public IInstanceDataAccessor GetPreviousDataAccessor()
return this;
}

public LayoutEvaluatorState? GetLayoutEvaluatorState()
public LayoutEvaluatorState GetLayoutEvaluatorState()
{
return _layoutEvaluatorState.Value;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
/// <summary>
/// Get component from componentModel
/// </summary>
[Obsolete("You need to get a context, not a component", true)]

Check warning on line 105 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAD&open=AZ5pGoG5x0LMOhZibJAD&pullRequest=1753
public void GetComponent(string pageName, string componentId)
{
throw new NotSupportedException("GetComponent is not supported, use GetComponentContext instead.");
Expand Down Expand Up @@ -149,9 +149,9 @@
/// <summary>
/// Get a specific component context from the state
/// </summary>
[Obsolete(
"A context is not uniquely identified by componentId and rowIndexes, use GetComponentContext(string, ComponentContext) instead so that we get subform data element as well."
)]

Check warning on line 154 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAE&open=AZ5pGoG5x0LMOhZibJAE&pullRequest=1753
public async Task<ComponentContext?> GetComponentContext(
string? pageName,
string componentId,
Expand Down Expand Up @@ -267,9 +267,9 @@
private static string GetInstanceOwnerPartyType(InstanceOwner? instanceOwner)
{
return !string.IsNullOrWhiteSpace(instanceOwner?.OrganisationNumber) ? "org"
: !string.IsNullOrWhiteSpace(instanceOwner?.PersonNumber) ? "person"
: !string.IsNullOrWhiteSpace(instanceOwner?.Username) ? "selfIdentified"
: "unknown";

Check warning on line 272 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAF&open=AZ5pGoG5x0LMOhZibJAF&pullRequest=1753

Check warning on line 272 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAG&open=AZ5pGoG5x0LMOhZibJAG&pullRequest=1753
}

/// <summary>
Expand Down Expand Up @@ -301,7 +301,7 @@
{
var formDataWrapper =
await DataAccessor.GetFormDataWrapper(binding, context.DataElementIdentifier)
?? throw new NullReferenceException($"No data element found for {binding.DataType} {binding.Field}");

Check warning on line 304 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Exception type System.NullReferenceException is reserved by the runtime

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAK&open=AZ5pGoG5x0LMOhZibJAK&pullRequest=1753

Check warning on line 304 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAH&open=AZ5pGoG5x0LMOhZibJAH&pullRequest=1753

if (formDataWrapper.DataElement == null)
{
Expand All @@ -323,9 +323,9 @@
/// <summary>
/// Return a full dataModelBinding from a context aware binding by adding indexes
/// </summary>
[Obsolete(
"This method is deprecated and will be removed in a future version in favor of AddInidicies(ModelBinding, ComponentContext)."
)]

Check warning on line 328 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAI&open=AZ5pGoG5x0LMOhZibJAI&pullRequest=1753
public async Task<DataReference> AddInidicies(
ModelBinding binding,
DataElementIdentifier dataElementIdentifier,
Expand All @@ -334,7 +334,7 @@
{
var formDataWrapper =
await DataAccessor.GetFormDataWrapper(binding, dataElementIdentifier)
?? throw new NullReferenceException($"No data element found for {binding.DataType} {binding.Field}");

Check warning on line 337 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'System.NullReferenceException' should not be thrown by user code.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAJ&open=AZ5pGoG5x0LMOhZibJAJ&pullRequest=1753

Check warning on line 337 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Exception type System.NullReferenceException is reserved by the runtime

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoG5x0LMOhZibJAL&open=AZ5pGoG5x0LMOhZibJAL&pullRequest=1753

if (formDataWrapper.DataElement == null)
{
Expand Down Expand Up @@ -445,7 +445,7 @@
return _componentModel?.DefaultDataType;
}

internal LayoutEvaluatorState? WithDataAccessor(IInstanceDataAccessor dataAccessor)
internal LayoutEvaluatorState WithDataAccessor(IInstanceDataAccessor dataAccessor)
{
return new LayoutEvaluatorState(
dataAccessor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,18 @@
private readonly string? _gatewayAction;
private readonly IFormDataWrapper _data;

public SingleDataElementAccessor(
Instance instance,
DataElement dataElement,
ApplicationMetadata applicationMetadata,
IFormDataWrapper data,
LayoutModel layouts,
FrontEndSettings frontEndSettings,
ITranslationService translationService,
string? gatewayAction,
string? taskId,
string? language
)

Check warning on line 68 in src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorStateInitializer.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Constructor has 10 parameters, which is greater than the 7 authorized.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ5pGoHZx0LMOhZibJAM&open=AZ5pGoHZx0LMOhZibJAM&pullRequest=1753
{
Instance = instance;
_dataElement = dataElement;
Expand Down Expand Up @@ -115,7 +115,7 @@
throw new NotSupportedException("Legacy single data accessor does not implement GetPreviousDataAccessor");
}

public LayoutEvaluatorState? GetLayoutEvaluatorState()
public LayoutEvaluatorState GetLayoutEvaluatorState()
{
return new LayoutEvaluatorState(
this,
Expand Down
89 changes: 85 additions & 4 deletions src/Altinn.App.Core/Internal/Pdf/PdfService.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Globalization;
using System.Text.Json;
using Altinn.App.Core.Configuration;
using Altinn.App.Core.Features;
using Altinn.App.Core.Features.Auth;
using Altinn.App.Core.Helpers.Extensions;
using Altinn.App.Core.Internal.App;
using Altinn.App.Core.Internal.Data;
using Altinn.App.Core.Internal.Expressions;
using Altinn.App.Core.Internal.Texts;
using Altinn.App.Core.Models;
using Altinn.App.Core.Models.Expressions;
Expand All @@ -21,6 +24,14 @@
/// </summary>
public class PdfService : IPdfService
{
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip,
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

private readonly IDataClient _dataClient;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IPdfGeneratorClient _pdfGeneratorClient;
Expand All @@ -29,6 +40,7 @@
private readonly IAuthenticationContext _authenticationContext;
private readonly ITranslationService _translationService;
private readonly GeneralSettings _generalSettings;
private readonly IAppResources _resources;
private readonly InstanceDataUnitOfWorkInitializer? _instanceDataUnitOfWorkInitializer;
private readonly Telemetry? _telemetry;
internal const string PdfElementType = "ref-data-as-pdf";
Expand All @@ -46,6 +58,7 @@
ILogger<PdfService> logger,
IAuthenticationContext authenticationContext,
ITranslationService translationService,
IAppResources resources,
IServiceProvider? serviceProvider = null,
Telemetry? telemetry = null
)
Expand All @@ -58,6 +71,7 @@
_logger = logger;
_authenticationContext = authenticationContext;
_translationService = translationService;
_resources = resources;
_instanceDataUnitOfWorkInitializer = serviceProvider?.GetService<InstanceDataUnitOfWorkInitializer>();
_telemetry = telemetry;
}
Expand Down Expand Up @@ -206,7 +220,7 @@
}
else if (displayFooter)
{
footerContent = await GetFooterContent(instance, language);
footerContent = await GetFooterContent(instance, taskId, language);
}

Stream pdfContent = await _pdfGeneratorClient.GeneratePdf(uri, footerContent, ct);
Expand Down Expand Up @@ -257,7 +271,7 @@
{
foreach (KeyValuePair<string, string> param in additionalQueryParams)
{
url += $"&{param.Key}={param.Value}";

Check warning on line 274 in src/Altinn.App.Core/Internal/Pdf/PdfService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a StringBuilder instead.

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ4rWmAn55rYU4SfOGPR&open=AZ4rWmAn55rYU4SfOGPR&pullRequest=1753
}
}

Expand Down Expand Up @@ -338,7 +352,7 @@
</div>";
}

private async Task<string> GetFooterContent(Instance instance, string? language)
private async Task<string> GetFooterContent(Instance instance, string taskId, string? language)
{
TimeZoneInfo timeZone = TimeZoneInfo.Utc;
try
Expand All @@ -353,15 +367,19 @@

DateTimeOffset now = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, timeZone);

string title = await _translationService.TranslateTextKey("appName", language) ?? "Altinn";
bool hideAppName = await GetHideAppNameInPdf(instance, taskId, language);

string dateGenerated = now.ToString("dd.MM.yyyy HH:mm", new CultureInfo("nb-NO"));
string altinnReferenceId = instance.Id.Split("/")[1].Split("-")[4];

string title = hideAppName
? string.Empty
: $"<span>{await _translationService.TranslateTextKey("appName", language) ?? "Altinn"}</span>";

string footerTemplate =
$@"<div style='font-family: Inter; font-size: 12px; width: 100%; display: flex; flex-direction: row; align-items: center; gap: 12px; padding: 0 70px 0 70px;'>
<div style='display: flex; flex-direction: row; width: 100%; align-items: center'>
<span>{title}</span>
{title}
<div
id='header-template'
style='color: #F00; font-weight: 700; border: 1px solid #F00; padding: 6px 8px; margin-left: auto;'
Expand All @@ -379,6 +397,69 @@
return footerTemplate;
}

private async Task<bool> GetHideAppNameInPdf(Instance instance, string taskId, string? language)
{
string? layoutSets = _resources.GetLayoutSets();
if (string.IsNullOrEmpty(layoutSets))
return false;

try
{
using var jsonDoc = JsonDocument.Parse(
layoutSets,
new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }
);
var root = jsonDoc.RootElement;

if (
!root.TryGetProperty("uiSettings", out var uiSettings)
|| !uiSettings.TryGetProperty("hideAppNameInPdf", out var hideAppName)
)
return false;

if (hideAppName.ValueKind == JsonValueKind.True)
return true;
if (hideAppName.ValueKind == JsonValueKind.False)
return false;

if (_instanceDataUnitOfWorkInitializer is null)
{
_logger.LogWarning(
"Cannot evaluate hideAppNameInPdf expression: InstanceDataUnitOfWorkInitializer is not available"
);
return false;
}

var expression = hideAppName.Deserialize<Expression>(_jsonSerializerOptions);
var dataAccessor = await _instanceDataUnitOfWorkInitializer.Init(instance, taskId, language);
var state = dataAccessor.GetLayoutEvaluatorState();

var layoutSet = _resources.GetLayoutSetForTask(taskId);
DataElementIdentifier? dataElement = layoutSet?.DataType is { } dataType
? instance.Data?.Find(d => d.DataType == dataType)
: null;

var componentContext = new ComponentContext(
dataAccessor,
component: null,
rowIndices: null,
dataElementIdentifier: dataElement
);
var result = await ExpressionEvaluator.EvaluateExpression(state, expression, componentContext);
return result is true;
}
catch (JsonException e)
{
_logger.LogWarning(e, "Failed to evaluate hideAppNameInPdf, defaulting to showing app name");
return false;
}
catch (InvalidOperationException e)
{
_logger.LogWarning(e, "Failed to evaluate hideAppNameInPdf, defaulting to showing app name");
return false;
}
}

private static List<KeyValuePair<string, string>> CreateAutoPdfTaskIdsQueryParams(
List<string>? autoGeneratePdfForTaskIds
)
Expand Down
4 changes: 3 additions & 1 deletion test/Altinn.App.Api.Tests/Controllers/PdfControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Altinn.App.Core.Internal.AppModel;
using Altinn.App.Core.Internal.Auth;
using Altinn.App.Core.Internal.Data;
using Altinn.App.Core.Internal.Expressions;
using Altinn.App.Core.Internal.Instances;
using Altinn.App.Core.Internal.Language;
using Altinn.App.Core.Internal.Pdf;
Expand Down Expand Up @@ -89,7 +90,8 @@
generalSettingsOptions,
_logger.Object,
_authenticationContext.Object,
_translationService.Object
_translationService.Object,
_appResources.Object
);
return pdfService;
}
Expand Down Expand Up @@ -150,7 +152,7 @@
.Returns<HttpRequestMessage, CancellationToken>(
async (m, c) =>
{
requestBody = await m.Content!.ReadAsStringAsync();

Check warning on line 155 in test/Altinn.App.Api.Tests/Controllers/PdfControllerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Forward the 'c' parameter to the 'ReadAsStringAsync' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ4rWmCF55rYU4SfOGPS&open=AZ4rWmCF55rYU4SfOGPS&pullRequest=1753
return mockResponse;
}
);
Expand Down Expand Up @@ -223,7 +225,7 @@
.Returns<HttpRequestMessage, CancellationToken>(
async (m, c) =>
{
requestBody = await m.Content!.ReadAsStringAsync();

Check warning on line 228 in test/Altinn.App.Api.Tests/Controllers/PdfControllerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Forward the 'c' parameter to the 'ReadAsStringAsync' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ4rWmCF55rYU4SfOGPT&open=AZ4rWmCF55rYU4SfOGPT&pullRequest=1753
return mockResponse;
}
);
Expand Down Expand Up @@ -298,7 +300,7 @@
.Returns<HttpRequestMessage, CancellationToken>(
async (m, c) =>
{
requestBody = await m.Content!.ReadAsStringAsync();

Check warning on line 303 in test/Altinn.App.Api.Tests/Controllers/PdfControllerTests.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Forward the 'c' parameter to the 'ReadAsStringAsync' method or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token

See more on https://sonarcloud.io/project/issues?id=Altinn_app-lib-dotnet&issues=AZ4rWmCF55rYU4SfOGPU&open=AZ4rWmCF55rYU4SfOGPU&pullRequest=1753
return mockResponse;
}
);
Expand Down
Loading
Loading