Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Kiboards library #19

Merged
merged 10 commits into from
Oct 16, 2023
Merged
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 res/KiBoards.01.TestRuns.View.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"attributes":{"fieldAttrs":"{\"id.keyword\":{\"count\":2},\"summary.time\":{\"customLabel\":\"Time\",\"count\":2},\"context.version\":{\"count\":2},\"id\":{\"customLabel\":\"Id\",\"count\":5},\"summary.failed\":{\"customLabel\":\"Failed\",\"count\":2},\"summary.total\":{\"customLabel\":\"Total\",\"count\":2},\"userName\":{\"customLabel\":\"User\",\"count\":1},\"summary.skipped\":{\"customLabel\":\"Skipped\",\"count\":2},\"name\":{\"customLabel\":\"Name\",\"count\":3},\"summary.passed\":{\"customLabel\":\"Passed\",\"count\":1},\"hash\":{\"customLabel\":\"Hash\",\"count\":1},\"machineName\":{\"customLabel\":\"Machine\",\"count\":1},\"startTime\":{\"customLabel\":\"Started\"},\"status\":{\"customLabel\":\"Status\"}}","fieldFormatMap":"{\"summary.time\":{\"id\":\"duration\",\"params\":{\"outputFormat\":\"humanizePrecise\"}},\"status\":{\"id\":\"color\",\"params\":{\"parsedUrl\":{\"origin\":\"http://localhost:5601\",\"pathname\":\"/app/management/kibana/objects\",\"basePath\":\"\"},\"fieldType\":\"string\",\"colors\":[{\"range\":\"-Infinity:Infinity\",\"regex\":\"Failed\",\"text\":\"#E7664C\",\"background\":\"#ffffff\"},{\"range\":\"-Infinity:Infinity\",\"regex\":\"Passed\",\"text\":\"#54B399\",\"background\":\"#ffffff\"}]}},\"startTime\":{\"id\":\"date\"}}","fields":"[]","name":"Test Runs","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"startTime","title":"kiboards-testruns-*","typeMeta":"{}"},"coreMigrationVersion":"8.6.2","created_at":"2023-10-16T12:40:01.427Z","id":"ac6ead8c-f3af-455a-a126-ec584b688cd9","migrationVersion":{"index-pattern":"8.0.0"},"references":[],"type":"index-pattern","updated_at":"2023-10-16T12:44:43.131Z","version":"WzI5NDYsMl0="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
2 changes: 2 additions & 0 deletions res/KiBoards.02.TestRuns.Search.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{"attributes":{"columns":["id","name","status","summary.time","summary.total","summary.passed","summary.failed","summary.skipped","userName","machineName","hash"],"description":"","grid":{"columns":{"context.version":{"width":204},"id":{"width":299},"machineName":{"width":100},"name":{"width":128},"startTime":{"width":210},"status":{"width":100},"summary.failed":{"width":111},"summary.passed":{"width":100},"summary.skipped":{"width":84},"summary.time":{"width":211},"summary.total":{"width":125}}},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["startTime","desc"],["summary.failed","asc"]],"timeRestore":false,"title":"Test Runs","usesAdHocDataView":false},"coreMigrationVersion":"8.6.2","created_at":"2023-10-16T12:40:01.427Z","id":"e8a11710-6a85-11ee-a384-fbd389354190","migrationVersion":{"search":"8.0.0"},"references":[{"id":"ac6ead8c-f3af-455a-a126-ec584b688cd9","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","updated_at":"2023-10-16T12:40:01.427Z","version":"WzI4ODAsMl0="}
{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":1,"missingRefCount":0,"missingReferences":[]}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace KiBoards
{
internal static class TestExtensions
internal static class KiBoardsTestExtensions
{
public static string ComputeMD5(this string value) => BitConverter.ToString(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(value))).Replace("-", "").ToLower();
public static void WriteMessage(this IMessageSink messageSink, string message) => messageSink.OnMessage(new DiagnosticMessage(message));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[AttributeUsage(AttributeTargets.Assembly)]
public class TestStartupAttribute : Attribute
public class KiboardsTestStartupAttribute : Attribute
{
public string ClassName { get; set; }

public TestStartupAttribute(string className)
public KiboardsTestStartupAttribute(string className)
{
ClassName = className;
}
Expand Down
10 changes: 6 additions & 4 deletions src/KiBoards.Xunit/Services/KiBoardsTestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public KiBoardsTestRunner(IMessageSink messageSink)
Variables = new Dictionary<string, string>()
};

foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetCustomAttribute<TestStartupAttribute>() != null))
var startupAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetCustomAttribute<KiboardsTestStartupAttribute>() != null).ToArray();
foreach (var assembly in startupAssemblies)
Startup(assembly, messageSink);

foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
Expand All @@ -46,7 +47,6 @@ public KiBoardsTestRunner(IMessageSink messageSink)

var uriString = Environment.GetEnvironmentVariable("KIB_ELASTICSEARCH_HOST") ?? "http://localhost:9200";
var connectionSettings = new ConnectionSettings(new Uri(uriString));


var elasticClient = new ElasticClient(connectionSettings
.DefaultMappingFor<KiBoardsTestRun>(m => m
Expand All @@ -73,12 +73,14 @@ public KiBoardsTestRunner(IMessageSink messageSink)
private void Startup(Assembly assembly, IMessageSink messageSink)
{
try
{
var startup = assembly.GetCustomAttribute<TestStartupAttribute>();
{
var startup = assembly.GetCustomAttribute<KiboardsTestStartupAttribute>();
Type type = assembly.GetType(startup.ClassName);

if (type != null)
{
messageSink.WriteMessage($"Invoking {type.FullName}");

if (type.GetConstructor(new Type[] { typeof(string), typeof(IMessageSink) }) != null)
Activator.CreateInstance(type, _testRun.Id, messageSink);
else if (type.GetConstructor(new Type[] { typeof(string) }) != null)
Expand Down
6 changes: 1 addition & 5 deletions src/KiBoards.Xunit/TestFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ public TestFrameworkExecutor(AssemblyName assemblyName, ISourceInformationProvid
{
_testRunner = testRunner;
_diagnosticMessageSink = diagnosticMessageSink;
}


}

protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
{
Expand All @@ -53,7 +51,6 @@ protected override async void RunTestCases(IEnumerable<IXunitTestCase> testCases
}



private class TestAssemblyRunner : XunitTestAssemblyRunner
{
private readonly KiBoardsTestRunner _testRunner;
Expand Down Expand Up @@ -107,7 +104,6 @@ protected override Task<RunSummary> RunTestMethodAsync(ITestMethod testMethod, I
}



private class TestResultBus : IMessageBus
{
private readonly IMessageBus _messageBus;
Expand Down
8 changes: 7 additions & 1 deletion src/KiBoards.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KiBoards.Xunit", "KiBoards.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KiBoards.Tests", "KiBoards.Tests\KiBoards.Tests.csproj", "{BD23E63C-CC78-4714-A319-E492CC90178A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleTest", "SimpleTest\SimpleTest.csproj", "{EF1D5D1A-6836-4F3C-9BAB-9FD5BEE0EEB5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleTest", "SimpleTest\SimpleTest.csproj", "{EF1D5D1A-6836-4F3C-9BAB-9FD5BEE0EEB5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KiBoards", "KiBoards\KiBoards.csproj", "{BA545090-C497-4AF5-835E-54BFFC1031D1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -27,6 +29,10 @@ Global
{EF1D5D1A-6836-4F3C-9BAB-9FD5BEE0EEB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF1D5D1A-6836-4F3C-9BAB-9FD5BEE0EEB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF1D5D1A-6836-4F3C-9BAB-9FD5BEE0EEB5}.Release|Any CPU.Build.0 = Release|Any CPU
{BA545090-C497-4AF5-835E-54BFFC1031D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA545090-C497-4AF5-835E-54BFFC1031D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA545090-C497-4AF5-835E-54BFFC1031D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA545090-C497-4AF5-835E-54BFFC1031D1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
11 changes: 11 additions & 0 deletions src/KiBoards/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Xunit.Abstractions;
using Xunit.Sdk;

namespace KiBoards
{
internal static class Extensions
{
public static void WriteMessage(this IMessageSink messageSink, string message) => messageSink.OnMessage(new DiagnosticMessage(message));
public static void WriteException(this IMessageSink messageSink, Exception exception) => messageSink.OnMessage(new DiagnosticMessage("{0}\n{1}", exception.Message, exception.StackTrace));
}
}
58 changes: 58 additions & 0 deletions src/KiBoards/KiBoards.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<BaseOutputPath>..\..\bin</BaseOutputPath>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>10</LangVersion>
<IsPackable>true</IsPackable>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>

<PropertyGroup>
<Authors>Matt Janda</Authors>
<PackageIcon>icon.png</PackageIcon>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageId>KiBoards</PackageId>
<AssemblyName>KiBoards</AssemblyName>
<RootNamespace>KiBoards</RootNamespace>
<Description>KiBoards offers the capability to visualise test cases and test run in Kibana.</Description>
<PackageTags>KiBoards Xunit Kibana Elasticsearch</PackageTags>
<RepositoryUrl>https://github.com/Jandini/KiBoards</RepositoryUrl>
<PackageProjectUrl>https://github.com/Jandini/KiBoards</PackageProjectUrl>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\icon.png" Pack="true" PackagePath="" />
<None Include="..\..\LICENSE" Pack="true" PackagePath="" />
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup>

<PackageReference Include="GitVersion.MsBuild" Version="5.12.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\KiBoards.Xunit\KiBoards.Xunit.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="KiBoards.02.TestRuns.Search.ndjson">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="KiBoards.01.TestRuns.View.ndjson">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions src/KiBoards/KiBoardsSavedObjectsAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public class KiBoardsSavedObjectsAttribute : Attribute
{
public bool Overwrite { get; set; }
public string SearchPattern { get; set; }

public KiBoardsSavedObjectsAttribute(string searchPattern = "*.ndjson")
{
SearchPattern = searchPattern;
}
}
8 changes: 8 additions & 0 deletions src/KiBoards/Models/Objects/ImportObjectsErrors.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace KiBoards.Models.Objects
{
class ImportObjectsErrors
{
public string Type { get; set; }
public string Id { get; set; }
}
}
9 changes: 9 additions & 0 deletions src/KiBoards/Models/Objects/ImportSavedObjectsResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace KiBoards.Models.Objects
{
class ImportObjectsResponse
{
public int SuccessCount { get; set; }
public bool Success { get; set; }
public List<ImportObjectsErrors> Errors { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/KiBoards/Models/Settings/KibanaSettingsChanges.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;

namespace KiBoards.Models.Settings
{
class KibanaSettingsChanges
{
[JsonPropertyName("theme:darkMode")]
public bool? ThemeDarkMode { get; set; }
}
}
7 changes: 7 additions & 0 deletions src/KiBoards/Models/Settings/KibanaSettingsRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace KiBoards.Models.Settings
{
class KibanaSettingsRequest
{
public KibanaSettingsChanges Changes { get; set; }
}
}
7 changes: 7 additions & 0 deletions src/KiBoards/Models/Status/KibanaStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace KiBoards.Models.Status
{
class KibanaStatus
{
public KibanaStatusOverall Overall { get; set; }
}
}
8 changes: 8 additions & 0 deletions src/KiBoards/Models/Status/KibanaStatusOverall.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace KiBoards.Models.Status
{
class KibanaStatusOverall
{
public string Level { get; set; }
public string Summary { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/KiBoards/Models/Status/KibanaStatusResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace KiBoards.Models.Status
{
class KibanaStatusResponse
{
public string Name { get; set; }
public string Uuid { get; set; }
public KibanaVersion Version { get; set; }
public KibanaStatus Status { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/KiBoards/Models/Status/KibanaVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace KiBoards.Models.Status
{
class KibanaVersion
{
public string Number { get; set; }
public string BuildHash { get; set; }
public int BuildNumber { get; set; }
public bool BuildSnapshot { get; set; }
}
}
47 changes: 47 additions & 0 deletions src/KiBoards/Services/KiBoardsKibanaClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System.Net.Http.Json;
using System.Text.Json;
using KiBoards.Models.Objects;
using KiBoards.Models.Settings;
using KiBoards.Models.Status;

namespace KiBoards.Services
{
internal class KiBoardsKibanaClient
{
private readonly HttpClient _httpClient;

public KiBoardsKibanaClient(Uri kibanaUri, HttpClient httpClinet)
{
_httpClient = httpClinet;
_httpClient.BaseAddress = kibanaUri;
_httpClient.DefaultRequestHeaders.Add("kbn-xsrf", "true");
}

public async Task SetDarkModeAsync(bool darkMode, CancellationToken cancellationToken)
{
var content = JsonContent.Create(new KibanaSettingsRequest() { Changes = new KibanaSettingsChanges() { ThemeDarkMode = darkMode } });
var response = await _httpClient.PostAsync("api/kibana/settings", content);
response.EnsureSuccessStatusCode();
}


public async Task<ImportObjectsResponse> ImportSavedObjectsAsync(string ndjsonFile) => await ImportSavedObjectsAsync(ndjsonFile, false, CancellationToken.None);
public async Task<ImportObjectsResponse> ImportSavedObjectsAsync(string ndjsonFile, bool overwrite) => await ImportSavedObjectsAsync(ndjsonFile, overwrite, CancellationToken.None);
public async Task<ImportObjectsResponse> ImportSavedObjectsAsync(string ndjsonFile, bool overwrite, CancellationToken cancellationToken)
{
var multipartContent = new MultipartFormDataContent();
var streamContent = new StreamContent(File.Open(ndjsonFile, FileMode.Open));
multipartContent.Add(streamContent, "file", ndjsonFile);
var response = await _httpClient.PostAsync($"/api/saved_objects/_import?overwrite={overwrite.ToString().ToLower()}", multipartContent);

response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<ImportObjectsResponse>(new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }, cancellationToken);

return result;
}


public async Task<KibanaStatusResponse> GetStatus() => await GetStatus(CancellationToken.None);
public async Task<KibanaStatusResponse> GetStatus(CancellationToken cancellationToken) => await _httpClient.GetFromJsonAsync<KibanaStatusResponse>("api/status", cancellationToken);
}
}
66 changes: 66 additions & 0 deletions src/KiBoards/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using KiBoards.Services;
using System.Reflection;
using Xunit.Abstractions;

[assembly: KiboardsTestStartup("KiBoards.Startup")]

namespace KiBoards
{
public class Startup
{

public Startup(IMessageSink messageSink)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetCustomAttribute<KiBoardsSavedObjectsAttribute>() != null);

foreach (var assembly in assemblies)
{
var attribute = assembly.GetCustomAttribute<KiBoardsSavedObjectsAttribute>();

var task = Task.Factory.StartNew(async () =>
{
var httpClient = new HttpClient();
var kibanaUri = new Uri(Environment.GetEnvironmentVariable("KIB_KIBANA_HOST") ?? "http://localhost:5601");
var kibanaClient = new KiBoardsKibanaClient(kibanaUri, httpClient);

messageSink.WriteMessage($"Waiting for Kibana {kibanaUri}");

while (true)
{
try
{
var response = await kibanaClient.GetStatus(CancellationToken.None);

string level = response?.Status?.Overall?.Level ?? throw new Exception("Kibana status is not available.");

if (level != "available")
throw new Exception("Kibana not available.");

messageSink.WriteMessage($"Kibana status: {level}");
break;
}
catch (Exception ex)
{
messageSink.WriteMessage(ex.Message);
await Task.Delay(5000);
}
}

var ndjsonFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), attribute.SearchPattern);

messageSink.WriteMessage($"Found {ndjsonFiles.Length} ndjson file(s)");

foreach (var ndjsonFile in ndjsonFiles.OrderBy(a => a))
{
messageSink.WriteMessage($"Imporing {Path.GetFileName(ndjsonFile)}");
var results = await kibanaClient.ImportSavedObjectsAsync(ndjsonFile, attribute.Overwrite);
messageSink.WriteMessage($"Imported {results.SuccessCount} object(s)");

if (!results.Success && results.SuccessCount > 0)
messageSink.WriteMessage("Warning: Some objects were not imported. Please ensure proper import order based on their dependencies.");
}
});
}
}
}
}
Loading