Skip to content

Commit

Permalink
Merge pull request #77 from HicServices/task/RDMP-1-jira
Browse files Browse the repository at this point in the history
Task/rdmp-1 Update Jira Plugin
  • Loading branch information
JFriel authored Jul 10, 2024
2 parents 9403777 + c942e6a commit cadb652
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ public void Dispose(IDataLoadEventListener listener, Exception pipelineFailureEx

cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 100000;

var cohortId = Convert.ToInt32(cmd.ExecuteScalar());

listener.OnNotify(this,
Expand Down
6 changes: 6 additions & 0 deletions HICPlugin/HICPlugin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
<PropertyGroup>
<PostBuildEvent />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>portable</DebugType>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\SharedAssemblyInfo.cs" Link="SharedAssemblyInfo.cs" />
</ItemGroup>
Expand Down
3 changes: 2 additions & 1 deletion InterfaceToJira/RestApiClient2/JiraClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public bool Execute(RestRequest request, HttpStatusCode expectedResponseCode)
if (restResponse.ResponseStatus != ResponseStatus.Completed || restResponse.StatusCode.IsError() || restResponse.ErrorException != null)
throw new JiraApiException(
$"RestSharp response status: {restResponse.ResponseStatus} - HTTP response: {restResponse.StatusCode} - {restResponse.StatusDescription} - {restResponse.Content}", restResponse.ErrorException);

return restResponse.Data;
}

Expand Down Expand Up @@ -207,7 +208,7 @@ public List<string> GetProjectNames()
{
Resource = "/rest/api/latest/project",
Method = Method.Get
}, HttpStatusCode.OK).Select((Func<Project, string>) (project => project.key)).ToList();
}, HttpStatusCode.OK).Select((Func<Project, string>)(project => project.key)).ToList();
list.Sort();
return list;
}
Expand Down
217 changes: 144 additions & 73 deletions JiraPlugin/JIRATicketingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,95 @@
using HIC.Common.InterfaceToJira.JIRA.RestApiClient2.JiraModel;
using Rdmp.Core.Ticketing;
using Rdmp.Core.ReusableLibraryCode.Checks;

using RestSharp;
using Microsoft.Win32;
using Rdmp.Core.Curation;
namespace JiraPlugin;

public class JIRATicketingSystem : PluginTicketingSystem
public partial class JIRATicketingSystem : PluginTicketingSystem
{
public static readonly Regex RegexForTickets = new(@"((?<!([A-Z]{1,10})-?)[A-Z]+-\d+)",RegexOptions.IgnoreCase|RegexOptions.Compiled);
public static readonly Regex RegexForTickets = new(@"((?<!([A-Z]{1,10})-?)[A-Z]+-\d+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

private const string RegexForUrlsPattern = @"^https://.*";
private static readonly Regex RegexForUrls = new(RegexForUrlsPattern,RegexOptions.Compiled);
private static readonly Regex RegexForUrls = MyRegex();

private JiraClient _client;

//releaseability
public List<Attachment> JIRAProjectAttachements { get; private set; }
public string JIRAReleaseTicketStatus { get; private set; }
private static readonly string[] PermissableReleaseStatusesForJIRAReleaseTickets = new[] { "Released" };

private void SetupIfRequired()
{
_client ??= new JiraClient(new JiraAccount(new JiraApiConfiguration
{
ServerUrl = _serverUrl,
User = _username,
Password = Credentials.GetDecryptedPassword(),
ApiUrl = _baseUrl
}));
}

private string GetStatusOfJIRATicket(string ticket)
{
var issue = GetIssue(ticket) ?? throw new Exception($"Non existent ticket: {ticket}");
return issue.fields.status.name;
}


private void GetAttachementsOfJIRATicket(string ticket)
{
var issue = GetIssue(ticket) ?? throw new Exception($"Non existent ticket: {ticket}");
JIRAProjectAttachements = issue.fields.attachment;
}

public override List<string> GetAvailableStatuses()
{
try
{
SetupIfRequired();
var statuses = _client.GetStatuses().Select(x => x.name);
return statuses.ToList();
}
catch (Exception)
{
return [];
}
}

private Issue GetIssue(string ticket)
{
return _client.GetIssue(ticket);
}
private readonly string _serverUrl;
private readonly string _apiVersion;
private readonly string _username;
private readonly string _baseUrl;

public JIRATicketingSystem(TicketingSystemConstructorParameters parameters) : base(parameters)
{

Credentials = parameters.Credentials;
Url = parameters.Url;
_serverUrl = parameters.Url;
_username = parameters.Credentials.Username;
_apiVersion = "latest";
_baseUrl = string.Format("{0}/rest/api/{1}/", _serverUrl, _apiVersion);
}

public override void Check(ICheckNotifier notifier)
{
if (Credentials == null)
notifier.OnCheckPerformed(new CheckEventArgs("Data Access credentials for JIRA are not set",CheckResult.Fail));
if (string.IsNullOrWhiteSpace(Url))
notifier.OnCheckPerformed(new CheckEventArgs("You must put in a URL to the JIRA server e.g. https://jira-hic.cmdn.dundee.ac.uk",CheckResult.Fail));
notifier.OnCheckPerformed(new CheckEventArgs("Data Access credentials for JIRA are not set", CheckResult.Fail));

if (string.IsNullOrWhiteSpace(_baseUrl))
notifier.OnCheckPerformed(new CheckEventArgs("You must put in a URL to the JIRA server e.g. https://example.atlassian.net", CheckResult.Fail));
else
if (RegexForUrls.IsMatch(Url))
if (RegexForUrls.IsMatch(_baseUrl))
notifier.OnCheckPerformed(new CheckEventArgs("Url matches RegexForUrls", CheckResult.Success));
else
notifier.OnCheckPerformed(
new CheckEventArgs(
$"Url {Url} does not match the regex RegexForUrls: {RegexForUrlsPattern}",
$"Url {_baseUrl} does not match the regex RegexForUrls: {RegexForUrlsPattern}",
CheckResult.Fail));
try
{
Expand All @@ -68,46 +120,7 @@ public override void Check(ICheckNotifier notifier)
}
}

public override bool IsValidTicketName(string ticketName)
{
//also let user clear tickets :)
return string.IsNullOrWhiteSpace(ticketName) || RegexForTickets.IsMatch(ticketName);
}

public override void NavigateToTicket(string ticketName)
{
if (string.IsNullOrWhiteSpace(ticketName))
return;
try
{
Check(ThrowImmediatelyCheckNotifier.Quiet);
}
catch (Exception e)
{
throw new Exception("JIRATicketingSystem Checks() failed (see inner exception for details)",e);
}


Uri navigationUri = null;
Uri baseUri = null;
var relativePath = $"/browse/{ticketName}";
try
{
baseUri = new Uri(Url);
navigationUri = new Uri(baseUri, relativePath);
Process.Start(navigationUri.AbsoluteUri);
}
catch (Exception e)
{
if(navigationUri != null)
throw new Exception($"Failed to navigate to {navigationUri.AbsoluteUri}", e);

if (baseUri != null)
throw new Exception($"Failed to reach {relativePath} from {baseUri.AbsoluteUri}", e);
}
}

public override TicketingReleaseabilityEvaluation GetDataReleaseabilityOfTicket(string masterTicket, string requestTicket, string releaseTicket, out string reason, out Exception exception)
public override TicketingReleaseabilityEvaluation GetDataReleaseabilityOfTicket(string masterTicket, string requestTicket, string releaseTicket, List<TicketingSystemReleaseStatus> acceptedStatuses, out string reason, out Exception exception)
{
exception = null;
try
Expand All @@ -128,7 +141,7 @@ public override TicketingReleaseabilityEvaluation GetDataReleaseabilityOfTicket(
return TicketingReleaseabilityEvaluation.NotReleaseable;
}

if(string.IsNullOrWhiteSpace(requestTicket))
if (string.IsNullOrWhiteSpace(requestTicket))
{
reason = "Request JIRA ticket is blank";
return TicketingReleaseabilityEvaluation.NotReleaseable;
Expand All @@ -153,12 +166,13 @@ public override TicketingReleaseabilityEvaluation GetDataReleaseabilityOfTicket(
return e.Message.Contains("Authentication Required") ? TicketingReleaseabilityEvaluation.CouldNotAuthenticateAgainstServer : TicketingReleaseabilityEvaluation.CouldNotReachTicketingServer;

}
var statusStrings = acceptedStatuses.Select(s => s.Status).ToList<string>();

//if it isn't at required status
if (!PermissableReleaseStatusesForJIRAReleaseTickets.Contains(JIRAReleaseTicketStatus))
if (!statusStrings.Contains(JIRAReleaseTicketStatus))
{
reason =
$"Status of release ticket ({JIRAReleaseTicketStatus}) was not one of the permissable release ticket statuses: {string.Join(",", PermissableReleaseStatusesForJIRAReleaseTickets)}";
$"Status of release ticket ({JIRAReleaseTicketStatus}) was not one of the permissable release ticket statuses: {string.Join(",", statusStrings)}";

return TicketingReleaseabilityEvaluation.NotReleaseable; //it cannot be released
}
Expand All @@ -182,37 +196,94 @@ public override TicketingReleaseabilityEvaluation GetDataReleaseabilityOfTicket(
public override string GetProjectFolderName(string masterTicket)
{
SetupIfRequired();
var issue = _client.GetIssue(masterTicket, new[] {"summary", "customfield_13400"});
var issue = _client.GetIssue(masterTicket, new[] { "summary", "customfield_13400" });

return issue.fields.customfield_13400;
}

private void SetupIfRequired()
public override bool IsValidTicketName(string ticketName)
{
_client ??= new JiraClient(new JiraAccount(new JiraApiConfiguration
{
ServerUrl = Url,
User = Credentials.Username,
Password = Credentials.GetDecryptedPassword()
}));
//also let user clear tickets :)
return string.IsNullOrWhiteSpace(ticketName) || RegexForTickets.IsMatch(ticketName);
}

private string GetStatusOfJIRATicket(string ticket)
public override void NavigateToTicket(string ticketName)
{
var issue = GetIssue(ticket) ?? throw new Exception($"Non existent ticket: {ticket}");
return issue.fields.status.name;
}
if (string.IsNullOrWhiteSpace(ticketName))
return;
try
{
Check(ThrowImmediatelyCheckNotifier.Quiet);
}
catch (Exception e)
{
throw new Exception("JIRATicketingSystem Checks() failed (see inner exception for details)", e);
}

Uri navigationUri = null;
Uri baseUri = null;
var relativePath = $"/browse/{ticketName}";
try
{
baseUri = new Uri(Url);
navigationUri = new Uri(baseUri, relativePath);
string browserPath = GetBrowserPath();
if (browserPath == string.Empty)
browserPath = "iexplore";
Process process = new()
{
StartInfo = new ProcessStartInfo(browserPath)
};
process.StartInfo.Arguments = "\"" + navigationUri.AbsoluteUri + "\"";
process.Start();
}
catch (Exception e)
{
if (navigationUri != null)
throw new Exception($"Failed to navigate to {navigationUri.AbsoluteUri}", e);

private void GetAttachementsOfJIRATicket(string ticket)
{
var issue = GetIssue(ticket) ?? throw new Exception($"Non existent ticket: {ticket}");
JIRAProjectAttachements = issue.fields.attachment;
if (baseUri != null)
throw new Exception($"Failed to reach {relativePath} from {baseUri.AbsoluteUri}", e);
}
}

private Issue GetIssue(string ticket)
private static string GetBrowserPath()
{
return _client.GetIssue(ticket);
string browser = string.Empty;
RegistryKey key = null;

try
{
// try location of default browser path in XP
key = Registry.ClassesRoot.OpenSubKey(@"HTTP\shell\open\command", false);

// try location of default browser path in Vista
if (key == null)
{
key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http", false); ;

Check warning on line 263 in JiraPlugin/JIRATicketingSystem.cs

View workflow job for this annotation

GitHub Actions / package

This call site is reachable on all platforms. 'Registry.CurrentUser' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)

Check warning on line 263 in JiraPlugin/JIRATicketingSystem.cs

View workflow job for this annotation

GitHub Actions / package

This call site is reachable on all platforms. 'RegistryKey.OpenSubKey(string, bool)' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
}

if (key != null)
{
//trim off quotes
browser = key.GetValue(null).ToString().ToLower().Replace("\"", "");

Check warning on line 269 in JiraPlugin/JIRATicketingSystem.cs

View workflow job for this annotation

GitHub Actions / package

This call site is reachable on all platforms. 'RegistryKey.GetValue(string?)' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
if (!browser.EndsWith("exe"))
{
//get rid of everything after the ".exe"
browser = browser.Substring(0, browser.LastIndexOf(".exe") + 4);
}

key.Close();

Check warning on line 276 in JiraPlugin/JIRATicketingSystem.cs

View workflow job for this annotation

GitHub Actions / package

This call site is reachable on all platforms. 'RegistryKey.Close()' is only supported on: 'windows'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1416)
}
}
catch
{
return string.Empty;
}

return browser;
}

[GeneratedRegex(RegexForUrlsPattern, RegexOptions.Compiled)]
private static partial Regex MyRegex();
}
2 changes: 1 addition & 1 deletion RDMP
Submodule RDMP updated 32 files
+4 −2 .github/workflows/build.yml
+6 −1 CHANGELOG.md
+1 −1 Directory.Build.props
+1 −1 Directory.Packages.props
+4 −4 Rdmp.Core.Tests/DataLoad/Engine/Integration/RemoteDatabaseAttacherTests.cs
+18 −18 Rdmp.Core.Tests/DataLoad/Modules/Attachers/RemoteTableAttacherTests.cs
+1 −1 Rdmp.Core/Curation/Data/EncryptedString.cs
+10 −1 Rdmp.Core/Curation/Data/TicketingSystemConfiguration.cs
+48 −0 Rdmp.Core/Curation/TicketingSystemReleaseStatus.cs
+3 −3 Rdmp.Core/DataExport/DataRelease/ReleaseEnvironmentPotential.cs
+19 −0 Rdmp.Core/Databases/CatalogueDatabase/up/085_AddTicketingReleaseStatuses.sql
+7 −4 Rdmp.Core/Icons/IconProvision/CatalogueIcons.resx
+2 −1 Rdmp.Core/Icons/IconProvision/RDMPConcept.cs
+ Rdmp.Core/Icons/TicketingSystemReleaseStatus.png
+2 −0 Rdmp.Core/Rdmp.Core.csproj
+6 −1 Rdmp.Core/Ticketing/ITicketingSystem.cs
+6 −1 Rdmp.Core/Ticketing/PluginTicketingSystem.cs
+8 −1 Rdmp.Core/Ticketing/SimpleTicketingSystem.cs
+1 −2 Rdmp.Core/Ticketing/TicketingSystemConstructorParameters.cs
+1 −0 Rdmp.UI.Tests/DesignPatternTests/ClassFileEvaluation/DocumentationCrossExaminationTest.cs
+263 −419 Rdmp.UI/ExtractionUIs/JoinsAndLookups/LookupConfigurationUI.Designer.cs
+227 −363 Rdmp.UI/ExtractionUIs/JoinsAndLookups/LookupConfigurationUI.cs
+446 −25 Rdmp.UI/ExtractionUIs/JoinsAndLookups/LookupConfigurationUI.resx
+255 −237 Rdmp.UI/LocationsMenu/Ticketing/TicketingSystemConfigurationUI.Designer.cs
+38 −0 Rdmp.UI/LocationsMenu/Ticketing/TicketingSystemConfigurationUI.cs
+25 −25 Rdmp.UI/LocationsMenu/Ticketing/TicketingSystemConfigurationUI.resx
+1 −1 Rdmp.UI/ProjectUI/ExtractionConfigurationUI.cs
+8 −2 Rdmp.UI/Rules/BinderWithErrorProviderFactory.cs
+3 −3 SharedAssemblyInfo.cs
+11 −4 Tests.Common/UnitTests.cs
+1 −1 directory.build.props
+1 −1 rdmp-client.xml

0 comments on commit cadb652

Please sign in to comment.