diff --git a/src/SuperDumpSelector/SuperDumpSelector.csproj b/src/SuperDumpSelector/SuperDumpSelector.csproj
index e21b630..2972b7e 100644
--- a/src/SuperDumpSelector/SuperDumpSelector.csproj
+++ b/src/SuperDumpSelector/SuperDumpSelector.csproj
@@ -95,7 +95,7 @@
2.3.0
- 1.0.0
+ 1.0.2
4.3.1
diff --git a/src/SuperDumpService.Test.Fakes/FakeDumpStorage.cs b/src/SuperDumpService.Test.Fakes/FakeDumpStorage.cs
index efc73d2..70cfe43 100644
--- a/src/SuperDumpService.Test.Fakes/FakeDumpStorage.cs
+++ b/src/SuperDumpService.Test.Fakes/FakeDumpStorage.cs
@@ -33,6 +33,8 @@ public class FakeDumpStorage : IDumpStorage, IBundleStorage {
public bool DelaysEnabled { get; set; }
+ public FakeDumpStorage() : this(Enumerable.Empty()) { }
+
public FakeDumpStorage(IEnumerable fakeDumps) {
this.fakeDumpsDict = new Dictionary();
this.fakeBundlesDict = new Dictionary();
diff --git a/src/SuperDumpService.Test.Fakes/FakeJiraApiService.cs b/src/SuperDumpService.Test.Fakes/FakeJiraApiService.cs
index 0591f8a..dc0e6f7 100644
--- a/src/SuperDumpService.Test.Fakes/FakeJiraApiService.cs
+++ b/src/SuperDumpService.Test.Fakes/FakeJiraApiService.cs
@@ -12,7 +12,11 @@ public class FakeJiraApiService : IJiraApiService {
private readonly object sync = new object();
public void SetFakeJiraIssues(string bundleId, IEnumerable jiraIssueModels) {
- jiraIssueStore[bundleId] = jiraIssueModels;
+ if (jiraIssueModels == null) {
+ jiraIssueStore.Remove(bundleId, out var x);
+ } else {
+ jiraIssueStore[bundleId] = jiraIssueModels;
+ }
}
public Task> GetBulkIssues(IEnumerable issueKeys) {
diff --git a/src/SuperDumpService.Test/JiraIssueRepositoryTest.cs b/src/SuperDumpService.Test/JiraIssueRepositoryTest.cs
index f53e94c..8c05487 100644
--- a/src/SuperDumpService.Test/JiraIssueRepositoryTest.cs
+++ b/src/SuperDumpService.Test/JiraIssueRepositoryTest.cs
@@ -68,6 +68,7 @@ public async Task TestJiraIssueRepository() {
// population
await jiraIssueStorage.Store("bundle1", new List { new JiraIssueModel("JRA-1111") });
await jiraIssueStorage.Store("bundle2", new List { new JiraIssueModel("JRA-2222"), new JiraIssueModel("JRA-3333") });
+ await jiraIssueStorage.Store("bundle9", new List { new JiraIssueModel("JRA-9999") });
await bundleRepo.Populate();
await jiraIssueRepository.Populate();
@@ -81,14 +82,17 @@ public async Task TestJiraIssueRepository() {
item => Assert.Equal("JRA-2222", item.Key),
item => Assert.Equal("JRA-3333", item.Key));
+ Assert.Collection(jiraIssueRepository.GetIssues("bundle9"),
+ item => Assert.Equal("JRA-9999", item.Key));
+
Assert.Empty(jiraIssueRepository.GetIssues("bundle3"));
// fake, that in jira some bundles have been referenced in new issues
- jiraApiService.SetFakeJiraIssues("bundle1", new JiraIssueModel[] { new JiraIssueModel("JRA-1111") });
- jiraApiService.SetFakeJiraIssues("bundle2", new JiraIssueModel[] { new JiraIssueModel("JRA-2222"), new JiraIssueModel("JRA-3333"), new JiraIssueModel("JRA-4444") });
- jiraApiService.SetFakeJiraIssues("bundle3", new JiraIssueModel[] { new JiraIssueModel("JRA-1111"), new JiraIssueModel("JRA-5555") });
+ jiraApiService.SetFakeJiraIssues("bundle1", new JiraIssueModel[] { new JiraIssueModel("JRA-1111") }); // same
+ jiraApiService.SetFakeJiraIssues("bundle2", new JiraIssueModel[] { new JiraIssueModel("JRA-2222"), new JiraIssueModel("JRA-4444") }); // one added, one removed
+ jiraApiService.SetFakeJiraIssues("bundle3", new JiraIssueModel[] { new JiraIssueModel("JRA-1111"), new JiraIssueModel("JRA-5555") }); // new
+ jiraApiService.SetFakeJiraIssues("bundle9", null ); // removed jira issues
- // trigger update of repository
await jiraIssueRepository.SearchBundleIssuesAsync(bundleRepo.GetAll(), true);
Assert.Collection(jiraIssueRepository.GetIssues("bundle1"),
@@ -96,15 +100,15 @@ public async Task TestJiraIssueRepository() {
Assert.Collection(jiraIssueRepository.GetIssues("bundle2"),
item => Assert.Equal("JRA-2222", item.Key),
- item => Assert.Equal("JRA-3333", item.Key),
item => Assert.Equal("JRA-4444", item.Key));
Assert.Collection(jiraIssueRepository.GetIssues("bundle3"),
item => Assert.Equal("JRA-1111", item.Key),
item => Assert.Equal("JRA-5555", item.Key));
+ Assert.Empty(jiraIssueRepository.GetIssues("bundle9"));
- var res = await jiraIssueRepository.GetAllIssuesByBundleIdsWithoutWait(new string[] { "bundle1", "bundle2", "bundle7", "bundle666" });
+ var res = await jiraIssueRepository.GetAllIssuesByBundleIdsWithoutWait(new string[] { "bundle1", "bundle2", "bundle7", "bundle666", "bundle9" });
Assert.Equal(2, res.Count());
Assert.Collection(res["bundle1"],
@@ -112,8 +116,12 @@ public async Task TestJiraIssueRepository() {
Assert.Collection(res["bundle2"],
item => Assert.Equal("JRA-2222", item.Key),
- item => Assert.Equal("JRA-3333", item.Key),
item => Assert.Equal("JRA-4444", item.Key));
+
+
+ Assert.Empty(jiraIssueRepository.GetIssues("bundle7"));
+ Assert.Empty(jiraIssueRepository.GetIssues("bundle666"));
+ Assert.Empty(jiraIssueRepository.GetIssues("bundle9"));
}
private IEnumerable CreateFakeDumps() {
diff --git a/src/SuperDumpService/JiraIntegrationSettings.cs b/src/SuperDumpService/JiraIntegrationSettings.cs
index b4e83bb..265f8d1 100644
--- a/src/SuperDumpService/JiraIntegrationSettings.cs
+++ b/src/SuperDumpService/JiraIntegrationSettings.cs
@@ -11,6 +11,7 @@ public class JiraIntegrationSettings {
public int JiraBundleSearchLimit { get; set; }
public double JiraBundleSearchDelay { get; set; }
public TimeSpan JiraBundleSearchTimeSpan { get; set; }
+ public string JiraApiAuthUrl { get; set; }
public string JiraApiSearchUrl { get; set; }
public string JiraApiUsername { get; set; }
public string JiraApiPassword { get; set; }
diff --git a/src/SuperDumpService/Models/DumpIdentifier.cs b/src/SuperDumpService/Models/DumpIdentifier.cs
index 6b7b9cc..36057a8 100644
--- a/src/SuperDumpService/Models/DumpIdentifier.cs
+++ b/src/SuperDumpService/Models/DumpIdentifier.cs
@@ -61,7 +61,6 @@ private sealed class DumpIdentifierPool {
public DumpIdentifier Allocate(string bundleId, string dumpId) {
int hash = $"{bundleId}:{dumpId}".GetHashCode();
- if (pool.TryGetValue(hash, out DumpIdentifier id)) return id; // fast path
lock (sync) {
if (pool.TryGetValue(hash, out DumpIdentifier id2)) return id2;
var newId = new DumpIdentifier(bundleId, dumpId);
diff --git a/src/SuperDumpService/Services/JiraApiService.cs b/src/SuperDumpService/Services/JiraApiService.cs
index 950e3d0..f01a415 100644
--- a/src/SuperDumpService/Services/JiraApiService.cs
+++ b/src/SuperDumpService/Services/JiraApiService.cs
@@ -3,6 +3,7 @@
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
+using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
@@ -13,6 +14,26 @@
using SuperDumpService.Models;
namespace SuperDumpService.Services {
+ ///
+ /// Datastructure that Jira returns on authentication
+ ///
+ public class SessionInfo {
+ public Session session;
+ public LoginInfo loginInfo;
+
+ public class Session {
+ public string name;
+ public string value;
+ }
+
+ public class LoginInfo {
+ public int failedLoginCount;
+ public int loginCount;
+ public DateTime lastFailedLoginTime;
+ public DateTime previousLoginTime;
+ }
+ }
+
public class JiraApiService : IJiraApiService {
private const string JsonMediaType = "application/json";
private const string JiraIssueFields = "status,resolution";
@@ -21,13 +42,49 @@ public class JiraApiService : IJiraApiService {
private readonly JiraIntegrationSettings settings;
private readonly HttpClient client;
+ public CookieContainer Cookies {
+ get { return HttpClientHandler.CookieContainer; }
+ set { HttpClientHandler.CookieContainer = value; }
+ }
+
+ public HttpClientHandler HttpClientHandler { get; set; }
+
+ public SessionInfo Session { get; set; }
+
public JiraApiService(IOptions settings) {
this.settings = settings.Value.JiraIntegrationSettings;
if (this.settings == null) return;
- client = new HttpClient();
+
+ HttpClientHandler = new HttpClientHandler {
+ AllowAutoRedirect = true,
+ UseCookies = true,
+ CookieContainer = new CookieContainer()
+ };
+
+ client = new HttpClient(HttpClientHandler);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(JsonMediaType));
- client.DefaultRequestHeaders.Authorization = GetBasicAuthenticationHeader(this.settings.JiraApiUsername, this.settings.JiraApiPassword);
+ }
+
+ private async Task Authenticate() {
+ using (var authClient = new HttpClient()) {
+ var uriBuilder = new UriBuilder(settings.JiraApiAuthUrl);
+ var response = await authClient.PostAsJsonAsync(settings.JiraApiAuthUrl, new {
+ username = this.settings.JiraApiUsername,
+ password = this.settings.JiraApiPassword
+ });
+ var sessionInfo = await response.Content.ReadAsAsync();
+ this.Session = sessionInfo;
+ var cookieDomain = new Uri(new Uri(settings.JiraApiAuthUrl).GetLeftPart(UriPartial.Authority));
+ this.Cookies.Add(cookieDomain, new Cookie(sessionInfo.session.name, sessionInfo.session.value));
+ }
+ }
+
+ private async Task EnsureAuthentication() {
+ // reauthenticate every 10 minutes
+ if (Session == null || (DateTime.Now - Session.loginInfo.previousLoginTime).Minutes > 10) {
+ await Authenticate();
+ }
}
public async Task> GetJiraIssues(string bundleId) {
@@ -49,10 +106,12 @@ private async Task> JiraSearch(string queryString) {
query["fields"] = JiraIssueFields;
uriBuilder.Query = query.ToString();
+ await EnsureAuthentication();
return await HandleResponse(await client.GetAsync(uriBuilder.ToString()));
}
- private async Task> JiraPostSearch(string queryString) {
+ private async Task> JiraPostSearch(string queryString, int retry = 3) {
+ await EnsureAuthentication();
return await HandleResponse(await client.PostAsJsonAsync(settings.JiraApiSearchUrl, new {
jql = queryString,
fields = JiraIssueFieldsArray
@@ -64,11 +123,9 @@ private async Task> HandleResponse(HttpResponseMessa
throw new HttpRequestException($"Jira api call {response.RequestMessage.RequestUri} returned status code {response.StatusCode}");
}
IEnumerable issues = (await response.Content.ReadAsAsync()).Issues;
-
foreach (JiraIssueModel issue in issues) {
issue.Url = settings.JiraIssueUrl + issue.Key;
}
-
return issues;
}
diff --git a/src/SuperDumpService/Services/JiraIssueRepository.cs b/src/SuperDumpService/Services/JiraIssueRepository.cs
index e759356..dd76d34 100644
--- a/src/SuperDumpService/Services/JiraIssueRepository.cs
+++ b/src/SuperDumpService/Services/JiraIssueRepository.cs
@@ -21,7 +21,7 @@ public class JiraIssueRepository {
private readonly IdenticalDumpRepository identicalDumpRepository;
private readonly JiraIntegrationSettings settings;
private readonly ILogger logger;
- private readonly ConcurrentDictionary> bundleIssues = new ConcurrentDictionary>();
+ private readonly ConcurrentDictionary> bundleIssues = new ConcurrentDictionary>();
public bool IsPopulated { get; private set; } = false;
public JiraIssueRepository(IOptions settings,
@@ -47,7 +47,7 @@ public async Task Populate() {
try {
IEnumerable jiraIssues = await jiraIssueStorage.Read(bundle.BundleId);
if (jiraIssues != null) {
- bundleIssues[bundle.BundleId] = jiraIssues;
+ bundleIssues[bundle.BundleId] = jiraIssues.ToList();
}
} catch (Exception e) {
logger.LogError("error reading jira-issue file: " + e.ToString());
@@ -61,7 +61,7 @@ public async Task Populate() {
}
public IEnumerable GetIssues(string bundleId) {
- return bundleIssues.GetValueOrDefault(bundleId, Enumerable.Empty())
+ return bundleIssues.GetValueOrDefault(bundleId, Enumerable.Empty().ToList())
.ToList();
}
@@ -85,7 +85,7 @@ public async Task>> GetAllIssues
public async Task WipeJiraIssueCache() {
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
try {
- foreach (KeyValuePair> item in bundleIssues) {
+ foreach (KeyValuePair> item in bundleIssues) {
jiraIssueStorage.Wipe(item.Key);
}
bundleIssues.Clear();
@@ -105,7 +105,7 @@ public async Task RefreshAllIssuesAsync() {
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
try {
//Only update bundles with unresolved issues
- IEnumerable>> bundlesToRefresh =
+ IEnumerable>> bundlesToRefresh =
bundleIssues.Where(bundle => bundle.Value.Any(issue => issue.GetStatusName() != "Resolved"));
if (!bundlesToRefresh.Any()) {
@@ -171,7 +171,7 @@ public async Task SearchBundleIssuesAsync(IEnumerable bundles, b
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
try {
IEnumerable bundlesToSearch = force ? bundles :
- bundles.Where(bundle => !bundleIssues.TryGetValue(bundle.BundleId, out IEnumerable issues) || !issues.Any()); //All bundles without issues
+ bundles.Where(bundle => !bundleIssues.TryGetValue(bundle.BundleId, out IList issues) || !issues.Any()); //All bundles without issues
await Task.WhenAll(bundlesToSearch.Select(bundle => SearchBundleAsync(bundle, force)));
} finally {
@@ -180,26 +180,32 @@ public async Task SearchBundleIssuesAsync(IEnumerable bundles, b
}
private async Task SearchBundleAsync(BundleMetainfo bundle, bool force) {
- IEnumerable jiraIssues;
+ IList jiraIssues;
if (!force && bundle.CustomProperties.TryGetValue(settings.CustomPropertyJiraIssueKey, out string jiraIssue)) {
jiraIssues = new List() { new JiraIssueModel { Key = jiraIssue } };
} else {
- jiraIssues = await apiService.GetJiraIssues(bundle.BundleId);
+ jiraIssues = (await apiService.GetJiraIssues(bundle.BundleId)).ToList();
}
if (jiraIssues.Any()) {
- await jiraIssueStorage.Store(bundle.BundleId, bundleIssues[bundle.BundleId] = jiraIssues);
+ bundleIssues[bundle.BundleId] = jiraIssues;
+ await jiraIssueStorage.Store(bundle.BundleId, jiraIssues);
+ } else {
+ bundleIssues.Remove(bundle.BundleId, out var val);
+ await jiraIssueStorage.Store(bundle.BundleId, Enumerable.Empty());
}
}
- private async Task SetBundleIssues(IEnumerable>> bundlesToUpdate, IEnumerable refreshedIssues) {
+ private async Task SetBundleIssues(IEnumerable>> bundlesToUpdate, IEnumerable refreshedIssues) {
var issueDictionary = refreshedIssues.ToDictionary(issue => issue.Key, issue => issue);
//Select the issues for each bundle and store them in the bundleIssues Dictionary
//I am not sure if this is the best way to do this
var fileStorageTasks = new List();
- foreach (KeyValuePair> bundle in bundlesToUpdate) {
- IEnumerable issues = bundle.Value.Select(issue => issueDictionary[issue.Key]);
- fileStorageTasks.Add(jiraIssueStorage.Store(bundle.Key, bundleIssues[bundle.Key] = issues)); //update the issue file for the bundle
+ foreach (KeyValuePair> bundle in bundlesToUpdate) {
+ if (issueDictionary.ContainsKey(bundle.Key)) {
+ IList issues = bundle.Value.Select(issue => issueDictionary[issue.Key]).ToList();
+ fileStorageTasks.Add(jiraIssueStorage.Store(bundle.Key, bundleIssues[bundle.Key] = issues)); //update the issue file for the bundle
+ }
}
await Task.WhenAll(fileStorageTasks);