Skip to content

Commit

Permalink
Added XRay exporter (#21)
Browse files Browse the repository at this point in the history
* Added XRay project

* Added client

* Added section service

* Added attachment and testcases services

* Some fixes

* Added tests

* Added config

* Fixed project

* Fixed project

* Added readme

---------

Co-authored-by: Dmitry.Gridnev <[email protected]>
  • Loading branch information
gibiw and Dmitry.Gridnev authored Oct 30, 2023
1 parent 3bcb9d8 commit 09f4e4f
Show file tree
Hide file tree
Showing 34 changed files with 2,121 additions and 1 deletion.
48 changes: 48 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ env:
AZURE_DIR: ./Migrators/AzureExporter
ZEPHYR_SCALE_DIR: ./Migrators/ZephyrScaleExporter
ZEPHYR_SQUAD_DIR: ./Migrators/ZephyrSquadExporter
XRAY_DIR: ./Migrators/XRayExporter

jobs:
importer_build:
Expand Down Expand Up @@ -239,6 +240,53 @@ jobs:
--output ./publish \
--no-restore ${{ env.ZEPHYR_SQUAD_DIR }}
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: publish/${{ matrix.artifact_name }}
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
overwrite: true

xray_build:
needs: zephyr_squad_build
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: win-x64
artifact_name: XRayExporter.exe
asset_name: XRayExporter-win-x64-${{ github.event.release.tag_name }}.exe
- os: linux-x64
artifact_name: XRayExporter
asset_name: XRayExporter-linux-x64-${{ github.event.release.tag_name }}
- os: osx-x64
artifact_name: XRayExporter
asset_name: XRayExporter-osx-x64-${{ github.event.release.tag_name }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7

- name: Restore dependencies
run: dotnet restore ${{ env.XRAY_DIR }}

- name: Publish project
run: |
dotnet publish \
-r ${{ matrix.os }} \
--configuration Release \
--self-contained true \
--output ./publish \
--no-restore ${{ env.XRAY_DIR }}
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v2
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
./Migrators/AzureExporter,
./Migrators/ZephyrScaleExporter,
./Migrators/ZephyrSquadExporter,
./Migrators/XRayExporter,
./Migrators/Importer
]
steps:
Expand All @@ -39,6 +40,7 @@ jobs:
./Migrators/AzureExporterTests,
./Migrators/ZephyrScaleExporterTests,
./Migrators/ZephyrSquadExporterTests,
./Migrators/XRayExporterTests,
./Migrators/ImporterTests
]
steps:
Expand Down
12 changes: 12 additions & 0 deletions Migrators/Migrators.sln
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZephyrSquadExporter", "Zeph
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZephyrSquadExporterTests", "ZephyrSquadExporterTests\ZephyrSquadExporterTests.csproj", "{C2B65002-89C3-4FD6-A945-87379D552098}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XRayExporter", "XRayExporter\XRayExporter.csproj", "{1E3A8E07-1023-4A40-90DF-1A2CA6BB707C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XRayExporterTests", "XRayExporterTests\XRayExporterTests.csproj", "{563D5041-6E07-4D52-BBB9-C87245604E6F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -90,6 +94,14 @@ Global
{C2B65002-89C3-4FD6-A945-87379D552098}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C2B65002-89C3-4FD6-A945-87379D552098}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C2B65002-89C3-4FD6-A945-87379D552098}.Release|Any CPU.Build.0 = Release|Any CPU
{1E3A8E07-1023-4A40-90DF-1A2CA6BB707C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E3A8E07-1023-4A40-90DF-1A2CA6BB707C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E3A8E07-1023-4A40-90DF-1A2CA6BB707C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E3A8E07-1023-4A40-90DF-1A2CA6BB707C}.Release|Any CPU.Build.0 = Release|Any CPU
{563D5041-6E07-4D52-BBB9-C87245604E6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{563D5041-6E07-4D52-BBB9-C87245604E6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{563D5041-6E07-4D52-BBB9-C87245604E6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{563D5041-6E07-4D52-BBB9-C87245604E6F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
25 changes: 25 additions & 0 deletions Migrators/XRayExporter/App.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Microsoft.Extensions.Logging;
using XRayExporter.Services;

namespace XRayExporter;

public class App
{
private readonly ILogger<App> _logger;
private readonly IExportService _exportService;

public App(ILogger<App> logger, IExportService exportService)
{
_logger = logger;
_exportService = exportService;
}

public void Run(string[] args)
{
_logger.LogInformation("Starting application");

_exportService.ExportProject().Wait();

_logger.LogInformation("Ending application");
}
}
161 changes: 161 additions & 0 deletions Migrators/XRayExporter/Client/Client.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System.Net.Http.Headers;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using XRayExporter.Models;

namespace XRayExporter.Client;

public class Client : IClient
{
private readonly ILogger<Client> _logger;
private readonly HttpClient _httpClient;
private readonly string _projectKey;

public Client(ILogger<Client> logger, IConfiguration configuration)
{
_logger = logger;

var section = configuration.GetSection("xray");
var url = section["url"];
if (string.IsNullOrEmpty(url))
{
throw new ArgumentException("Url is not specified");
}

var token = section["token"];
if (string.IsNullOrEmpty(token))
{
throw new ArgumentException("Token is not specified");
}

var projectKey = section["projectKey"];
if (string.IsNullOrEmpty(projectKey))
{
throw new ArgumentException("Project key is not specified");
}

_projectKey = projectKey;
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(url);
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}

public async Task<JiraProject> GetProject()
{
_logger.LogInformation("Getting project {ProjectKey}", _projectKey);

var response = await _httpClient.GetAsync("rest/api/2/project");
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Failed to get project. Status code: {StatusCode}. Response: {Response}",
response.StatusCode, await response.Content.ReadAsStringAsync());

throw new Exception($"Failed to get project. Status code: {response.StatusCode}");
}

var content = await response.Content.ReadAsStringAsync();
var projects = JsonSerializer.Deserialize<List<JiraProject>>(content);
var project = projects!.FirstOrDefault(p =>
string.Equals(p.Key, _projectKey, StringComparison.InvariantCultureIgnoreCase));

if (project != null) return project;

_logger.LogError("Project not found");

throw new Exception("Project not found");
}

public async Task<List<XrayFolder>> GetFolders()
{
_logger.LogInformation("Getting folders for project {ProjectKey}", _projectKey);

var response =
await _httpClient.GetAsync($"rest/raven/1.0/api/testrepository/{_projectKey.ToUpper()}/folders");
if (!response.IsSuccessStatusCode)
{
_logger.LogError("Failed to get folders. Status code: {StatusCode}. Response: {Response}",
response.StatusCode, await response.Content.ReadAsStringAsync());

throw new Exception($"Failed to get folders. Status code: {response.StatusCode}");
}

var content = await response.Content.ReadAsStringAsync();
var folders = JsonSerializer.Deserialize<XRayFolders>(content);

return folders!.Folders;
}

public async Task<List<XRayTest>> GetTestFromFolder(int folderId)
{
_logger.LogInformation("Getting test from folder {FolderId}", folderId);

var response =
await _httpClient.GetAsync(
$"rest/raven/1.0/api/testrepository/{_projectKey.ToUpper()}/folders/{folderId}/tests");
if (!response.IsSuccessStatusCode)
{
_logger.LogError(
"Failed to get test from folder {FolderId}. Status code: {StatusCode}. Response: {Response}",
folderId, response.StatusCode, await response.Content.ReadAsStringAsync());

throw new Exception($"Failed to get test from folder {folderId}. Status code: {response.StatusCode}");
}

var content = await response.Content.ReadAsStringAsync();
var tests = JsonSerializer.Deserialize<XRayTests>(content);

return tests!.Tests;
}

public async Task<XRayTestFull> GetTest(string testKey)
{
_logger.LogInformation("Getting test {TestKey}", testKey);

var response =
await _httpClient.GetAsync(
$"rest/raven/1.0/api/test?keys={testKey}");
if (!response.IsSuccessStatusCode)
{
_logger.LogError(
"Failed to get test {TestKey}. Status code: {StatusCode}. Response: {Response}",
testKey, response.StatusCode, await response.Content.ReadAsStringAsync());

throw new Exception($"Failed to get test {testKey}. Status code: {response.StatusCode}");
}

var content = await response.Content.ReadAsStringAsync();
var tests = JsonSerializer.Deserialize<List<XRayTestFull>>(content);

return tests!.First();
}

public async Task<JiraItem> GetItem(string link)
{
_logger.LogInformation("Getting item {Link}", link);

var response =
await _httpClient.GetAsync(link.Split(_httpClient.BaseAddress.ToString())[1]);
if (!response.IsSuccessStatusCode)
{
_logger.LogError(
"Failed to get item {Link}. Status code: {StatusCode}. Response: {Response}",
link, response.StatusCode, await response.Content.ReadAsStringAsync());

throw new Exception($"Failed to get item {link}. Status code: {response.StatusCode}");
}

var content = await response.Content.ReadAsStringAsync();
var item = JsonSerializer.Deserialize<JiraItem>(content);

return item;
}

public async Task<byte[]> DownloadAttachment(string link)
{
_logger.LogInformation("Downloading attachment {Link}", link);

return
await _httpClient.GetByteArrayAsync(link.Split(_httpClient.BaseAddress.ToString())[1]);
}
}
13 changes: 13 additions & 0 deletions Migrators/XRayExporter/Client/IClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using XRayExporter.Models;

namespace XRayExporter.Client;

public interface IClient
{
Task<JiraProject> GetProject();
Task<List<XrayFolder>> GetFolders();
Task<List<XRayTest>> GetTestFromFolder(int folderId);
Task<XRayTestFull> GetTest(string testKey);
Task<JiraItem> GetItem(string link);
Task<byte[]> DownloadAttachment(string link);
}
9 changes: 9 additions & 0 deletions Migrators/XRayExporter/Models/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace XRayExporter.Models;

public static class Constants
{
public const string XrayReporter = "Xray Reporter";
public const string XrayType = "Xray Type";
public const string XrayStatus = "Xray Status";
public const string XrayArchived = "Xray Archived";
}
36 changes: 36 additions & 0 deletions Migrators/XRayExporter/Models/JiraItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Text.Json.Serialization;

namespace XRayExporter.Models;

public class JiraItem
{
[JsonPropertyName("fields")]
public Fields Fields { get; set; }
}

public class Fields
{
[JsonPropertyName("description")]
public string Description { get; set; }

[JsonPropertyName("attachment")]
public List<Attachment> Attachments { get; set; }

[JsonPropertyName("summary")]
public string Summary { get; set; }

[JsonPropertyName("labels")]
public List<string> Labels { get; set; }

[JsonPropertyName("issuelinks")]
public List<JiraLink> IssueLinks { get; set; }
}

public class Attachment
{
[JsonPropertyName("filename")]
public string FileName { get; set; }

[JsonPropertyName("content")]
public string Content { get; set; }
}
34 changes: 34 additions & 0 deletions Migrators/XRayExporter/Models/JiraLink.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Text.Json.Serialization;

namespace XRayExporter.Models;

public class JiraLink
{
[JsonPropertyName("type")]
public Type Type { get; set; }

[JsonPropertyName("inwardIssue")]
public Issue? InwardIssue { get; set; }

[JsonPropertyName("outwardIssue")]
public Issue? OutwardIssue { get; set; }
}

public class Type
{
[JsonPropertyName("name")]
public string Name { get; set; }

[JsonPropertyName("inward")]
public string Inward { get; set; }
}

public class Issue
{
[JsonPropertyName("key")]
public string Key { get; set; }

[JsonPropertyName("self")]
public string Self { get; set; }
}

Loading

0 comments on commit 09f4e4f

Please sign in to comment.