Skip to content

Commit 0fc87ee

Browse files
authored
Merge branch 'spr/main/cf35ff23' into spr/main/d6447eac
2 parents 82a53b8 + a9dc40e commit 0fc87ee

File tree

91 files changed

+1301
-301
lines changed

Some content is hidden

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

91 files changed

+1301
-301
lines changed

.github/workflows/gitea-designer-integration-tests.yaml .github/workflows/designer-gitea-and-db-integration-tests copy.yaml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Run gitea integration tests
1+
name: Run gitea and db integration tests
22
on:
33
push:
44
branches: [ main ]
@@ -16,7 +16,7 @@ on:
1616

1717
jobs:
1818
gitea-and-db-integration-tests:
19-
name: Run integration tests against actual gitea and db
19+
name: Run integration tests against actual gitea and dbs
2020
runs-on: ubuntu-latest
2121
env:
2222
DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE: false
@@ -32,6 +32,6 @@ jobs:
3232
run: |
3333
dotnet build backend/Designer.sln -v m
3434
35-
- name: Test gitea tests
35+
- name: Run integration tests
3636
run: |
3737
dotnet test backend/Designer.sln --filter "(Category=GiteaIntegrationTest)|(Category=DbIntegrationTest)" -v m

backend/packagegroups/NuGet.props

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
<ItemGroup Label="Shared nuget packages">
1111
<PackageReference Update="JsonSchema.Net" Version="5.5.1" />
1212
<PackageReference Update="CompilerAttributes" Version="1.1.2" />
13+
<PackageReference Update="Confluent.Kafka" Version="2.8.0" />
14+
<PackageReference Update="Confluent.SchemaRegistry" Version="2.8.0" />
15+
<PackageReference Update="Confluent.SchemaRegistry.Serdes.Json" Version="2.8.0" />
1316
<PackageReference Update="ini-parser-netstandard" Version="2.5.2" />
1417
<PackageReference Update="JWTCookieAuthentication" Version="4.0.4" />
1518
<PackageReference Update="LibGit2Sharp" Version="0.31.0" />

backend/src/Designer/Configuration/EidLoggerClientSettings.cs

-8
This file was deleted.

backend/src/Designer/Configuration/Extensions/ServiceCollectionExtensions.cs

+17
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,23 @@ private static void ConfigureSettingsTypeBySection<TOption>(this IServiceCollect
9191
services.TryAddScoped(typeof(TOption), svc => ((IOptionsSnapshot<object>)svc.GetService(typeof(IOptionsSnapshot<TOption>)))!.Value);
9292
}
9393

94+
public static IServiceCollection RegisterSettingsSingleton<TOption>(this IServiceCollection services, IConfiguration configuration, string section = null)
95+
where TOption : class, new()
96+
{
97+
string sectionName = string.IsNullOrWhiteSpace(section) ? typeof(TOption).Name : section;
98+
ConfigureSettingsTypeBySectionSingleton<TOption>(services, configuration, sectionName);
99+
100+
return services;
101+
}
102+
103+
private static void ConfigureSettingsTypeBySectionSingleton<TOption>(this IServiceCollection services, IConfiguration configuration, string sectionName)
104+
where TOption : class, new()
105+
{
106+
var options = new TOption();
107+
configuration.GetSection(sectionName).Bind(options);
108+
services.TryAddSingleton(typeof(TOption), _ => options);
109+
}
110+
94111
/// <summary>
95112
/// Register all the services that implement or inherit from the marker interface.
96113
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Altinn.Studio.Designer.Configuration;
2+
3+
public class KafkaSettings
4+
{
5+
public string BootstrapServers { get; set; }
6+
public string KafkaUserName { get; set; }
7+
public string KafkaPassword { get; set; }
8+
public string SchemaRegistryUrl { get; set; }
9+
public string SchemaRegistryUserName { get; set; }
10+
public string SchemaRegistryPassword { get; set; }
11+
public bool UseSaslSsl { get; set; }
12+
public string StatisticsTopic { get; set; }
13+
}

backend/src/Designer/Controllers/PreviewController.cs

+2-40
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Altinn.Platform.Storage.Interface.Models;
1414
using Altinn.Studio.Designer.Filters;
1515
using Altinn.Studio.Designer.Helpers;
16+
using Altinn.Studio.Designer.Helpers.Preview;
1617
using Altinn.Studio.Designer.Infrastructure.GitRepository;
1718
using Altinn.Studio.Designer.Models;
1819
using Altinn.Studio.Designer.Models.App;
@@ -61,8 +62,6 @@ public class PreviewController(IHttpContextAccessor httpContextAccessor,
6162

6263
// This value will be overridden to act as the task number for apps that use layout sets
6364
private const int PartyId = 51001;
64-
private const string MINIMUM_NUGET_VERSION = "8.0.0.0";
65-
private const int MINIMUM_PREVIEW_NUGET_VERSION = 15;
6665

6766
/// <summary>
6867
/// Default action for the preview.
@@ -142,7 +141,7 @@ public async Task<ActionResult<ApplicationMetadata>> ApplicationMetadata(string
142141
ApplicationMetadata applicationMetadata = await altinnAppGitRepository.GetApplicationMetadata(cancellationToken);
143142
string appNugetVersionString = _appDevelopmentService.GetAppLibVersion(AltinnRepoEditingContext.FromOrgRepoDeveloper(org, app, developer)).ToString();
144143
// This property is populated at runtime by the apps, so we need to mock it here
145-
applicationMetadata.AltinnNugetVersion = GetMockedAltinnNugetBuildFromVersion(appNugetVersionString);
144+
applicationMetadata.AltinnNugetVersion = NugetVersionHelper.GetMockedAltinnNugetBuildFromVersion(appNugetVersionString);
146145
applicationMetadata = SetMockedPartyTypesAllowedAsAllFalse(applicationMetadata);
147146
return Ok(applicationMetadata);
148147
}
@@ -695,29 +694,6 @@ private static string ReplaceIndexToFetchCorrectOrgAppInCshtml(string originalCo
695694
return modifiedContent;
696695
}
697696

698-
/// <summary>
699-
/// Method to get the mocked altinn nuget build from the version
700-
/// We are returnning the minimum BUILD version of the nuget package that is required for app frontend to work
701-
/// from v4 and above.
702-
/// </summary>
703-
/// <param name="version">The version of the nuget package</param>
704-
/// <returns>The minimum build version of the nuget package</returns>
705-
private string GetMockedAltinnNugetBuildFromVersion(string version)
706-
{
707-
708-
string[] versionParts = version.Split('.');
709-
if (!IsValidSemVerVersion(versionParts))
710-
{
711-
return string.Empty;
712-
}
713-
714-
if (IsPreviewVersion(versionParts) && GetPreviewVersion(versionParts) < MINIMUM_PREVIEW_NUGET_VERSION)
715-
{
716-
return string.Empty;
717-
}
718-
719-
return MINIMUM_NUGET_VERSION;
720-
}
721697

722698
/// <summary>
723699
/// Method to override the partyTypesAllowed in app metadata to bypass the check in app-frontend for a valid party during instantiation.
@@ -733,19 +709,5 @@ private static ApplicationMetadata SetMockedPartyTypesAllowedAsAllFalse(Applicat
733709
return applicationMetadata;
734710
}
735711

736-
private bool IsValidSemVerVersion(string[] versionParts)
737-
{
738-
return versionParts.Length >= 3 && Convert.ToInt32(versionParts[0]) >= 8;
739-
}
740-
741-
private bool IsPreviewVersion(string[] versionParts)
742-
{
743-
return versionParts[2].Contains("-preview") && versionParts.Length == 4;
744-
}
745-
746-
private int GetPreviewVersion(string[] versionParts)
747-
{
748-
return Convert.ToInt32(versionParts[3]);
749-
}
750712
}
751713
}

backend/src/Designer/Designer.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
<PackageReference Include="Altinn.Platform.Storage.Interface" />
2424
<PackageReference Include="Azure.Security.KeyVault.Secrets" />
2525
<PackageReference Include="CompilerAttributes" />
26+
<PackageReference Include="Confluent.Kafka" />
27+
<PackageReference Include="Confluent.SchemaRegistry" />
28+
<PackageReference Include="Confluent.SchemaRegistry.Serdes.Json" />
2629
<PackageReference Include="DistributedLock.Postgres" />
2730
<PackageReference Include="DotNetEnv" />
2831
<PackageReference Include="ini-parser-netstandard" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Altinn.Studio.Designer.Events;
7+
using Altinn.Studio.Designer.Models;
8+
using Altinn.Studio.Designer.Repository;
9+
using Altinn.Studio.Designer.Services.Interfaces;
10+
using Altinn.Studio.Designer.ViewModels.Request;
11+
using Altinn.Studio.Designer.ViewModels.Request.Enums;
12+
using MediatR;
13+
using Microsoft.AspNetCore.Hosting;
14+
15+
namespace Altinn.Studio.Designer.EventHandlers.DeploymentPipelineCompleted;
16+
17+
public class DeploymentPipelineCompletedStatisticsHandler : INotificationHandler<Events.DeploymentPipelineCompleted>
18+
{
19+
private readonly IDeploymentRepository _deploymentRepository;
20+
private readonly IKafkaProducer _kafkaProducer;
21+
private readonly IWebHostEnvironment _hostingEnvironment;
22+
23+
public DeploymentPipelineCompletedStatisticsHandler(IDeploymentRepository deploymentRepository, IWebHostEnvironment hostingEnvironment, IKafkaProducer kafkaProducer)
24+
{
25+
_deploymentRepository = deploymentRepository;
26+
_hostingEnvironment = hostingEnvironment;
27+
_kafkaProducer = kafkaProducer;
28+
}
29+
30+
public async Task Handle(Events.DeploymentPipelineCompleted notification, CancellationToken cancellationToken)
31+
{
32+
var studioStatistics = await CalculateStudioStatistics(notification);
33+
await _kafkaProducer.ProduceAsync(studioStatistics, cancellationToken);
34+
}
35+
36+
private async Task<StudioStatisticsModel> CalculateStudioStatistics(Events.DeploymentPipelineCompleted notification)
37+
{
38+
StudioStatisticsEvent studioEvent = await CalculateStudioStatisticsEvent(notification);
39+
return new StudioStatisticsModel
40+
{
41+
EventName = studioEvent.Name,
42+
Description = studioEvent.Description,
43+
Environment = notification.Environment,
44+
Org = notification.EditingContext.Org,
45+
App = notification.EditingContext.Repo,
46+
AdditionalData = new Dictionary<string, string>
47+
{
48+
{ "studioEnvironment", _hostingEnvironment.EnvironmentName }
49+
}
50+
};
51+
}
52+
53+
private async Task<StudioStatisticsEvent> CalculateStudioStatisticsEvent(Events.DeploymentPipelineCompleted notification)
54+
{
55+
if (notification.PipelineType != PipelineType.Deploy && notification.PipelineType != PipelineType.Undeploy)
56+
{
57+
throw new ArgumentException("PipelineType must be Deploy or Undeploy");
58+
}
59+
60+
if (notification.PipelineType == PipelineType.Undeploy)
61+
{
62+
return notification.Succeeded ? StudioStatisticsEvent.AppDecommissioned : StudioStatisticsEvent.AppDecommissionFailed;
63+
}
64+
65+
bool isUpdate = await IsAppUpdated(notification);
66+
67+
if (isUpdate)
68+
{
69+
return notification.Succeeded ? StudioStatisticsEvent.AppUpdated : StudioStatisticsEvent.AppUpdateFailed;
70+
}
71+
72+
return notification.Succeeded ? StudioStatisticsEvent.AppDeployed : StudioStatisticsEvent.AppDeployFailed;
73+
}
74+
75+
/// <summary>
76+
/// Calculates if the app is updated based on the deployment history
77+
/// Current deploy is also stored in the database.
78+
/// If the current deploy is successful we're checking if there is one more successful deploy in the environment other than the current one.
79+
/// If the current deploy is unsuccessful we're checking if there was one successful deploy in the environment.
80+
/// </summary>
81+
private async Task<bool> IsAppUpdated(Events.DeploymentPipelineCompleted notification)
82+
{
83+
// if the current deployment is successful it will be contained in the list of successful deployments
84+
var deploymentsInEnvironment = (await _deploymentRepository.GetSucceeded(
85+
notification.EditingContext.Org,
86+
notification.EditingContext.Repo,
87+
notification.Environment,
88+
new DocumentQueryModel { SortDirection = SortDirection.Descending }
89+
)).ToList();
90+
91+
return (notification.Succeeded && deploymentsInEnvironment.Count > 1) ||
92+
(!notification.Succeeded && deploymentsInEnvironment.Count > 0);
93+
}
94+
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace Altinn.Studio.Designer.EventHandlers.DeploymentPipelineCompleted;
2+
3+
public class StudioStatisticsEvent
4+
{
5+
public string Name { get; private set; }
6+
public string Description { get; private set; }
7+
private StudioStatisticsEvent(string name, string description)
8+
{
9+
Name = name;
10+
Description = description;
11+
}
12+
13+
public static StudioStatisticsEvent AppDeployed { get; } = new("AppDeployed", "App deployed");
14+
public static StudioStatisticsEvent AppUpdated { get; } = new("AppUpdated", "App updated");
15+
public static StudioStatisticsEvent AppDecommissioned { get; } = new("AppDecommissioned", "App Decommissioned");
16+
public static StudioStatisticsEvent AppDeployFailed { get; } = new("AppDeployFailed", "App deploy failed");
17+
public static StudioStatisticsEvent AppUpdateFailed { get; } = new("AppUpdateFailed", "App update failed");
18+
public static StudioStatisticsEvent AppDecommissionFailed { get; } = new("AppDecommissionFailed", "App decommission failed");
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Altinn.Studio.Designer.Models;
2+
using MediatR;
3+
4+
namespace Altinn.Studio.Designer.Events;
5+
6+
public record DeploymentPipelineCompleted : INotification
7+
{
8+
9+
public required AltinnRepoEditingContext EditingContext { get; init; }
10+
public required string Environment { get; init; }
11+
public required PipelineType PipelineType { get; init; }
12+
public required bool Succeeded { get; init; }
13+
}

backend/src/Designer/Events/DeploymentPipelineQueued.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace Altinn.Studio.Designer.Events;
55

6-
public class DeploymentPipelineQueued : INotification
6+
public record DeploymentPipelineQueued : INotification
77
{
88
public required AltinnRepoEditingContext EditingContext { get; set; }
99
public required int BuildId { get; set; }

backend/src/Designer/Helpers/AltinnStudioRepositoryScanner.cs

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ public static string FindRootDotEnvFilePath()
1111
return Path.Combine(FindRootDirectoryPath(), ".env");
1212
}
1313

14+
public static string FindKafkaComposerFilePath()
15+
{
16+
return Path.Combine(FindRootDirectoryPath(), "development", "kafka", "compose.yaml");
17+
}
18+
1419
public static string FindRootDirectoryPath([CallerFilePath] string filePath = "")
1520
{
1621
return GetDirectoryPathBySearchPattern(Path.GetDirectoryName(filePath), ".github");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System;
2+
using System.Diagnostics.Contracts;
3+
using System.Linq;
4+
5+
namespace Altinn.Studio.Designer.Helpers.Preview
6+
{
7+
public static class NugetVersionHelper
8+
{
9+
private const string MINIMUM_NUGET_VERSION = "8.0.0.0";
10+
private const int MINIMUM_PREVIEW_NUGET_VERSION = 15;
11+
12+
/// <summary>
13+
/// Method to get the mocked altinn nuget build from the version
14+
/// We are returnning the minimum BUILD version of the nuget package that is required for app frontend to work
15+
/// from v4 and above.
16+
/// </summary>
17+
/// <param name="version">The version of the nuget package</param>
18+
/// <returns>The minimum build version of the nuget package</returns>
19+
public static string GetMockedAltinnNugetBuildFromVersion(string version)
20+
{
21+
22+
string[] versionParts = version.Split('.');
23+
if (!IsValidSemVerVersion(versionParts))
24+
{
25+
return string.Empty;
26+
}
27+
28+
string normalVersion = version.Split('-').First();
29+
int[] numberVersion = [.. normalVersion.Split('.').Take(3).Select((split) => Convert.ToInt32(split))];
30+
if (IsVersionOrBelow(numberVersion, [8, 0, 0]) && IsPreviewVersion(versionParts) && GetPreviewVersion(versionParts) < MINIMUM_PREVIEW_NUGET_VERSION)
31+
{
32+
return string.Empty;
33+
}
34+
35+
return MINIMUM_NUGET_VERSION;
36+
}
37+
38+
private static bool IsValidSemVerVersion(string[] versionParts)
39+
{
40+
return versionParts.Length >= 3 && Convert.ToInt32(versionParts[0]) >= 8;
41+
}
42+
43+
// <exception cref="FormatException"></exception>
44+
// <exception cref="OverflowException"></exception>
45+
private static bool IsVersionOrBelow(int[] versionParts, int[] breakpoint)
46+
{
47+
Contract.Requires(versionParts.Length == 3);
48+
Contract.Requires(breakpoint.Length == 3);
49+
for (int i = 0; i < 3; i++)
50+
{
51+
if (versionParts[i] > breakpoint[i])
52+
{
53+
return false;
54+
}
55+
}
56+
return true;
57+
}
58+
59+
private static bool IsPreviewVersion(string[] versionParts)
60+
{
61+
return versionParts[2].Contains("-preview") && versionParts.Length == 4;
62+
}
63+
64+
private static int GetPreviewVersion(string[] versionParts)
65+
{
66+
return Convert.ToInt32(versionParts[3]);
67+
}
68+
}
69+
}

backend/src/Designer/Infrastructure/ServiceRegistration.cs

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ public static IServiceCollection RegisterServiceImplementations(this IServiceCol
7878
services.AddTransient<IImagesService, ImagesService>();
7979
services.AddTransient<ILayoutService, LayoutService>();
8080
services.RegisterDatamodeling(configuration);
81+
services.RegisterSettingsSingleton<KafkaSettings>(configuration);
82+
services.AddTransient<IKafkaProducer, KafkaProducer>();
8183

8284
return services;
8385
}

0 commit comments

Comments
 (0)