Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -361,3 +361,5 @@ AppsMonitoring/**/appsettings.Secret.json
AppsMonitoring/**/data.db
AppsMonitoring/infra/appsettings.*.json
AppsMonitoring/infra/**/result.yaml

AppsAnalysis/config.ini
1 change: 1 addition & 0 deletions AppsAnalysis/.csharpierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/repos/
34 changes: 34 additions & 0 deletions AppsAnalysis/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[*.cs]

# IDE0058: Expression value is never used
dotnet_diagnostic.IDE0058.severity = suggestion

# S125: Sections of code should not be commented out
dotnet_diagnostic.S125.severity = suggestion

# S112: General exceptions should never be thrown
dotnet_diagnostic.S112.severity = suggestion

# CA2201: Exception type is not sufficiently specific
dotnet_diagnostic.CA2201.severity = suggestion

# S1135: Track uses of "TODO" tags
dotnet_diagnostic.S1135.severity = suggestion

# S4487: Unread private class field
dotnet_diagnostic.S4487.severity = suggestion

# S1481: Unused local variables should be removed
dotnet_diagnostic.S1481.severity = suggestion

# CA1050: Declare types in namespaces
dotnet_diagnostic.CA1050.severity = suggestion

# S3903: Types should be defined in named namespaces
dotnet_diagnostic.S3903.severity = suggestion

# S108: Nested blocks of code should not be left empty
dotnet_diagnostic.S108.severity = suggestion

# S1199: Nested code blocks should not be used
dotnet_diagnostic.S1199.severity = suggestion
34 changes: 34 additions & 0 deletions AppsAnalysis/AppsAnalysis.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C17303EC-5744-4990-806D-9FAAD4040DE9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.Analysis", "src\Altinn.Analysis\Altinn.Analysis.csproj", "{108D9742-E8FE-4DA4-B8B9-E6CC849DD3ED}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.Analysis.Cli", "src\Altinn.Analysis.Cli\Altinn.Analysis.Cli.csproj", "{54883DAB-F56B-4005-871E-4BADA984405C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{108D9742-E8FE-4DA4-B8B9-E6CC849DD3ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{108D9742-E8FE-4DA4-B8B9-E6CC849DD3ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{108D9742-E8FE-4DA4-B8B9-E6CC849DD3ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{108D9742-E8FE-4DA4-B8B9-E6CC849DD3ED}.Release|Any CPU.Build.0 = Release|Any CPU
{54883DAB-F56B-4005-871E-4BADA984405C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{54883DAB-F56B-4005-871E-4BADA984405C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54883DAB-F56B-4005-871E-4BADA984405C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54883DAB-F56B-4005-871E-4BADA984405C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{108D9742-E8FE-4DA4-B8B9-E6CC849DD3ED} = {C17303EC-5744-4990-806D-9FAAD4040DE9}
{54883DAB-F56B-4005-871E-4BADA984405C} = {C17303EC-5744-4990-806D-9FAAD4040DE9}
EndGlobalSection
EndGlobal
32 changes: 32 additions & 0 deletions AppsAnalysis/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project>
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisMode>Recommended</AnalysisMode>
<Features>strict</Features>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<StripSymbols>false</StripSymbols>
<WarningsNotAsErrors>NU1901;NU1902;NU1903;NU1904</WarningsNotAsErrors>
</PropertyGroup>

<ItemGroup>
<!-- Formatting -->
<PackageReference Include="CSharpier.MsBuild" Version="0.30.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

<!-- Analyzers -->
<PackageReference Include="Nullable.Extended.Analyzer" Version="1.15.6169">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.3.0.106239">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
45 changes: 45 additions & 0 deletions AppsAnalysis/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Apps analysis

Meant to gather statistics on code from all apps in Altinn 3.

## Prereqs

A config file at `config.ini`
```sh
cp config.sample.ini config.ini
```
Make the appropriate changes.
The password needs to be a token from Altinn Studio with `Read` permissions for `organization` and `repository` scope.
See below:
![Access token config](img/access-token-config.png)

Example
```ini
username=martinothamar
password=8e************************
directory=C:\code\digdir\altinn\analysis
clear_directory=true
```

This would target Altinn Studio prod by default.

## Run

Go into CLI directory:
```sh
cd src/Altinn.Analysis.Cli
```

Fetch
```sh
dotnet run -c Release -- fetch -c ../../config.ini
```

![Downloading apps](img/download-progress.png)

Analyze
```sh
dotnet run -c Release -- analyze -c ../../config.ini
```

![Analyzing apps](img/analysis.png)
6 changes: 6 additions & 0 deletions AppsAnalysis/config.sample.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
directory=<path to the directory where the repos should be cloned>
altinn_url=<url to Altinn>
username=<username>
password=<password>
clear_directory=<true|false>
max_parallelism=<int>
Binary file added AppsAnalysis/img/access-token-config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AppsAnalysis/img/analysis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AppsAnalysis/img/download-progress.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions AppsAnalysis/src/Altinn.Analysis.Cli/Altinn.Analysis.Cli.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<!-- <IsAotCompatible>true</IsAotCompatible> -->
<!-- <PublishAot>true</PublishAot> -->
</PropertyGroup>

<ItemGroup>
<Compile Remove="repos/**/*.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="ConsoleAppFramework" Version="5.2.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Altinn.Analysis\Altinn.Analysis.csproj" />
</ItemGroup>

</Project>
158 changes: 158 additions & 0 deletions AppsAnalysis/src/Altinn.Analysis.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System.Diagnostics;
using System.Globalization;
using Altinn.Analysis;
using ConsoleAppFramework;
using Spectre.Console;

var app = ConsoleApp.Create();
app.Add<Commands>();
await app.RunAsync(args);

public class Commands
{
#pragma warning disable CA1822 // Mark members as static

/// <summary>Fetch Altinn apps into a local directory</summary>
/// <param name="username">-u, username for Altinn Studio.</param>
/// <param name="password">-p, password for Altinn Studio.</param>
/// <param name="directory">-d, local directory to place fetched apps.</param>
/// <param name="clearDirectory">-cd, clear directory before fetching.</param>
/// <param name="maxParallelism">-mp, make parallelism for running analysis (including fetching data).</param>
/// <param name="altinnUrl">-au, URL for Altinn Studio APIs.</param>
/// <param name="configFile">-c, Config file for this CLI.</param>
[Command("fetch")]
public async Task Fetch(
string? username = null,
string? password = null,
string? directory = null,
bool? clearDirectory = null,
int? maxParallelism = null,
string? altinnUrl = null,
string? configFile = null,
CancellationToken cancellationToken = default
)
{
var timer = Stopwatch.StartNew();
var config = await TryReadConfigFile(configFile, cancellationToken);
using var analyzer = new AppsFetcher(
new(
directory ?? config.Directory ?? "repos/",
username ?? config.Username ?? throw new Exception("Need username config to fetch"),
password ?? config.Password ?? throw new Exception("Need password config to fetch"),
maxParallelism ?? config.MaxParallelism ?? Environment.ProcessorCount,
clearDirectory ?? config.ClearDirectory ?? false,
altinnUrl ?? config.AltinnUrl ?? "https://altinn.studio"
)
);
await analyzer.Fetch(cancellationToken);
timer.Stop();
var elapsed = timer.Elapsed switch
{
var v when v >= TimeSpan.FromHours(1) => $"{v.TotalHours:0.00}h",
var v when v >= TimeSpan.FromMinutes(1) => $"{v.TotalMinutes:0.00}m",
var v => $"{v.TotalSeconds:0.00}s",
};
AnsiConsole.MarkupLine("");
AnsiConsole.MarkupLine($"[green]Fetch completed in {elapsed}[/]");
}

/// <summary>Analyze Altinn apps in a directory</summary>
/// <param name="directory">-d, local directory to place fetched apps.</param>
/// <param name="maxParallelism">-mp, make parallelism for running analysis (including fetching data).</param>
/// <param name="configFile">-c, Config file for this CLI.</param>
[Command("analyze")]
public async Task Analyze(
string? directory = null,
int? maxParallelism = null,
string? configFile = null,
CancellationToken cancellationToken = default
)
{
var timer = Stopwatch.StartNew();
var config = await TryReadConfigFile(configFile, cancellationToken);
var analyzer = new AppsAnalyzer(
new(
directory ?? config.Directory ?? "repos/",
maxParallelism ?? config.MaxParallelism ?? Environment.ProcessorCount
)
);
await analyzer.Analyze(cancellationToken);
timer.Stop();
var elapsed = timer.Elapsed switch
{
var v when v >= TimeSpan.FromHours(1) => $"{v.TotalHours:0.00}h",
var v when v >= TimeSpan.FromMinutes(1) => $"{v.TotalMinutes:0.00}m",
var v => $"{v.TotalSeconds:0.00}s",
};
AnsiConsole.MarkupLine("");
AnsiConsole.MarkupLine($"[green]Analysis completed in {elapsed}[/]");
}

private static async Task<ConfigFile> TryReadConfigFile(
string? configFile,
CancellationToken cancellationToken
)
{
var result = new ConfigFile();

if (string.IsNullOrWhiteSpace(configFile))
return result;

if (!File.Exists(configFile))
throw new Exception($"Could not find config file at: '{configFile}'");

var lines = await File.ReadAllLinesAsync(configFile, cancellationToken);
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line))
continue;

if (line.Split('=') is not [var key, var value])
throw new Exception($"Invalid config at line: {line}");

switch (key)
{
case "directory":
result = result with { Directory = value };
break;
case "username":
result = result with { Username = value };
break;
case "password":
result = result with { Password = value };
break;
case "clear_directory":
if (!bool.TryParse(value, out var clearDirectory))
throw new Exception(
$"Unexpected value for 'clear_directory': '{value}' - expected boolean"
);
result = result with { ClearDirectory = clearDirectory };
break;
case "max_parallelism":
if (!int.TryParse(value, CultureInfo.InvariantCulture, out var maxParallelism))
throw new Exception(
$"Unexpected value for 'max_parallelism': '{value}' - expected integer"
);
result = result with { MaxParallelism = maxParallelism };
break;
case "altinn_url":
result = result with { AltinnUrl = value };
break;
default:
throw new Exception($"Unexpected key in config: '{key}'");
}
}

return result;
}

private readonly record struct ConfigFile
{
public string? Directory { get; init; }
public string? Username { get; init; }
public string? Password { get; init; }
public bool? ClearDirectory { get; init; }
public int? MaxParallelism { get; init; }
public string? AltinnUrl { get; init; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"profiles": {
"Fetch": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"commandLineArgs": "fetch -c $(MSBuildProjectDirectory)/../../config.ini"
},
"Analyze": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"commandLineArgs": "analyze -c $(MSBuildProjectDirectory)/../../config.ini"
},
}
}
17 changes: 17 additions & 0 deletions AppsAnalysis/src/Altinn.Analysis/Altinn.Analysis.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<!-- <IsAotCompatible>true</IsAotCompatible> -->
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LibGit2Sharp" Version="0.31.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="Buildalyzer" Version="7.1.0" />
<PackageReference Include="Buildalyzer.Workspaces" Version="7.1.0" />
</ItemGroup>

</Project>
Loading