274 lines
12 KiB
C#
274 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Mail;
|
|
using System.Web.Mvc;
|
|
using System.Collections;
|
|
using System.Collections.Concurrent;
|
|
using System.Threading.Tasks;
|
|
using System.Web.Script.Serialization;
|
|
using RestSharp;
|
|
using uRelease.Models;
|
|
using YouTrackSharp.Infrastructure;
|
|
using umbraco.NodeFactory;
|
|
using Issue = uRelease.Models.Issue;
|
|
using System.Configuration;
|
|
using Version = uRelease.Models.Version;
|
|
|
|
namespace uRelease.Controllers
|
|
{
|
|
public class ApiController : Controller
|
|
{
|
|
private const string VersionBundleUrl = "admin/customfield/versionBundle/Umbraco Versions";
|
|
private const string IssuesUrl = "issue/byproject/{0}?filter={1}&max=200";
|
|
|
|
private static readonly string ProjectId = ConfigurationManager.AppSettings["uReleaseProjectId"];
|
|
|
|
private static readonly string Login = ConfigurationManager.AppSettings["uReleaseUsername"];
|
|
private static readonly string Password = ConfigurationManager.AppSettings["uReleasePassword"];
|
|
private static readonly int ReleasesPageNodeId = int.Parse(ConfigurationManager.AppSettings["uReleaseParentNodeId"]);
|
|
private const string YouTrackJsonFile = "~/App_Data/YouTrack/all.json";
|
|
|
|
public JsonResult VersionBundle(string ids, bool cached)
|
|
{
|
|
var idArray = new ArrayList();
|
|
idArray.AddRange(ids.Replace("all", "").TrimEnd(',').Split(','));
|
|
idArray.Remove("");
|
|
|
|
// For each version in the bundle, go get the issues
|
|
var toReturn = new List<AggregateView>();
|
|
|
|
Version[] orderedVersions;
|
|
|
|
if (cached == false)
|
|
{
|
|
var gotBundle = GetVersionBundle();
|
|
|
|
orderedVersions = idArray.Count > 0
|
|
? gotBundle.Data.Versions.Where(x => idArray.Contains(x.Value)).OrderBy(x => x.Value.AsFullVersion()).ToArray()
|
|
: gotBundle.Data.Versions.OrderBy(x => x.Value.AsFullVersion()).ToArray();
|
|
}
|
|
else
|
|
{
|
|
orderedVersions = ids.Split(',').Select(versionId => new Version { Value = versionId }).ToArray();
|
|
}
|
|
|
|
var currentReleases = new List<Version>();
|
|
var plannedReleases = new List<Version>();
|
|
var inProgressReleases = new List<Version>();
|
|
|
|
var releasesNode = new Node(ReleasesPageNodeId);
|
|
|
|
foreach (Node release in releasesNode.Children)
|
|
{
|
|
var version = orderedVersions.SingleOrDefault(x => x.Value == release.Name);
|
|
if (version != null)
|
|
{
|
|
if (release.GetPropertyValue("recommendedRelease") == "1")
|
|
{
|
|
var status = release.GetProperty("releaseStatus");
|
|
if (status != null && status.Value != "Released")
|
|
version.ReleaseStatus = status.Value;
|
|
|
|
currentReleases.Add(version);
|
|
}
|
|
|
|
if (release.GetPropertyValue("releaseStatus") == "Planning")
|
|
plannedReleases.Add(version);
|
|
|
|
if (release.GetPropertyValue("releaseStatus") != "Released")
|
|
inProgressReleases.Add(version);
|
|
}
|
|
}
|
|
|
|
// Just used to make sure we don't make repeated API requests for keys
|
|
var versionCache = new ConcurrentDictionary<string, RestResponse<IssuesWrapper>>();
|
|
|
|
foreach (var version in orderedVersions)
|
|
{
|
|
var issueView = new List<IssueView>();
|
|
|
|
var activityView = new List<ActivityView>();
|
|
|
|
var item = new AggregateView();
|
|
|
|
if (cached == false)
|
|
{
|
|
item = new AggregateView
|
|
{
|
|
inProgressRelease = inProgressReleases.FirstOrDefault(x => x.Value == version.Value) != null,
|
|
version = version.Value,
|
|
isPatch = version.Value.AsFullVersion().Build != 0,
|
|
releaseDescription = version.Description ?? string.Empty,
|
|
releaseStatus = version.ReleaseStatus,
|
|
released = version.Released,
|
|
releaseDate = version.ReleaseDate == 0 ? "" : ConvertDate(version.ReleaseDate).ToString(CultureInfo.InvariantCulture),
|
|
currentRelease = currentReleases.FirstOrDefault(x => x.Value == version.Value) != null,
|
|
plannedRelease = plannedReleases.FirstOrDefault(x => x.Value == version.Value) != null
|
|
};
|
|
|
|
var issuesList = new List<Issue>();
|
|
|
|
var issues = versionCache.GetOrAdd(version.Value,
|
|
key => GetRestResponse<IssuesWrapper>(string.Format(IssuesUrl, ProjectId, "Due+in+version%3A+" + key)));
|
|
|
|
issuesList.AddRange(issues.Data.Issues);
|
|
|
|
Parallel.ForEach(
|
|
issuesList,
|
|
issue =>
|
|
{
|
|
var view = new IssueView
|
|
{
|
|
id = issue.Id,
|
|
state = GetFieldFromIssue(issue, "State"),
|
|
title = GetFieldFromIssue(issue, "summary"),
|
|
type = GetFieldFromIssue(issue, "Type"),
|
|
breaking = (GetFieldFromIssue(issue, "Backwards compatible?") == "No")
|
|
};
|
|
|
|
issueView.Add(view);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
var cache = (List<AggregateView>)GetVersionsFromFile().Data;
|
|
var cachedVersion = cache.FirstOrDefault(x => x.version == version.Value);
|
|
if (cachedVersion != null)
|
|
{
|
|
item = cachedVersion;
|
|
issueView = cachedVersion.issues.ToList();
|
|
}
|
|
}
|
|
|
|
var activitiesDateDesc = activityView.Where(x => x.changes.Any()).OrderByDescending(x => x.date);
|
|
var issueIdsFromActivities = activitiesDateDesc.Select(x => x.id).Distinct()
|
|
.Concat(issueView.Where(y => y != null && activitiesDateDesc.Select(z => z.id).Contains(y.id) == false)
|
|
.Select(y => y.id)); // Add issues for which there is no activity
|
|
|
|
item.issues = new List<IssueView>(issueIdsFromActivities.Select(x => issueView.Single(y => y != null && y.id == x)).OrderBy(x => x.id));
|
|
item.activities = activitiesDateDesc.Take(5).ToList();
|
|
|
|
toReturn.Add(item);
|
|
}
|
|
|
|
return new JsonResult { Data = toReturn, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
|
|
}
|
|
|
|
public JsonResult GetAllFromFile()
|
|
{
|
|
if (System.IO.File.Exists(Server.MapPath(YouTrackJsonFile)) == false)
|
|
SaveAllToFile();
|
|
|
|
var allText = System.IO.File.ReadAllText(Server.MapPath(YouTrackJsonFile));
|
|
|
|
return new JsonResult { Data = new JavaScriptSerializer().Deserialize<List<AggregateView>>(allText), JsonRequestBehavior = JsonRequestBehavior.AllowGet };
|
|
}
|
|
|
|
public JsonResult GetVersionsFromFile()
|
|
{
|
|
var allText = System.IO.File.ReadAllText(Server.MapPath(YouTrackJsonFile));
|
|
|
|
return new JsonResult { Data = new JavaScriptSerializer().Deserialize<List<AggregateView>>(allText), JsonRequestBehavior = JsonRequestBehavior.AllowGet };
|
|
}
|
|
|
|
public string SaveAllToFile()
|
|
{
|
|
try
|
|
{
|
|
var data = VersionBundle("all", false).Data;
|
|
var typedData = (List<AggregateView>)data;
|
|
if (typedData.Any())
|
|
{
|
|
var result = new JavaScriptSerializer().Serialize(data);
|
|
|
|
using (var streamWriter = new StreamWriter(Server.MapPath(YouTrackJsonFile), false))
|
|
{
|
|
streamWriter.WriteLine(result);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SendYouTrackErrorMail();
|
|
return string.Format("{0} There was no data to serialize so a new cache file hasn't been created", DateTime.Now);
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
return string.Format("{0} {1}", exception.Message, exception.StackTrace);
|
|
}
|
|
|
|
return string.Format("Results succesfully written to {0} at {1}", Server.MapPath(YouTrackJsonFile), DateTime.Now);
|
|
}
|
|
|
|
private static void SendYouTrackErrorMail()
|
|
{
|
|
var notify = ConfigurationManager.AppSettings["uForumSpamNotify"];
|
|
|
|
var body = string.Format("<p>Creating YouTrack cache has failed to get any data, there might be something wrong with YouTrack.</p>");
|
|
|
|
var mailMessage = new MailMessage
|
|
{
|
|
Subject = "Our - Issue tracker cache creation failed",
|
|
Body = body,
|
|
IsBodyHtml = true
|
|
};
|
|
|
|
foreach (var email in notify.Split(','))
|
|
mailMessage.To.Add(email);
|
|
|
|
mailMessage.From = new MailAddress("our@umbraco.org");
|
|
|
|
var smtpClient = new SmtpClient();
|
|
smtpClient.Send(mailMessage);
|
|
}
|
|
|
|
private static DateTime ConvertDate(long date)
|
|
{
|
|
return new DateTime(1970, 1, 1).AddMilliseconds(date);
|
|
}
|
|
|
|
private static string GetFieldFromIssue(Issue issue, string fieldName)
|
|
{
|
|
var findField = issue.Fields.FirstOrDefault(x => x.Name == fieldName);
|
|
return findField != null ? findField.Value : string.Empty;
|
|
}
|
|
|
|
public JsonResult AllVersions()
|
|
{
|
|
var gotBundle = GetVersionBundle();
|
|
return new JsonResult { Data = gotBundle.Data, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
|
|
}
|
|
|
|
private static RestResponse<VersionBundle> GetVersionBundle()
|
|
{
|
|
return GetRestResponse<VersionBundle>(VersionBundleUrl);
|
|
}
|
|
|
|
private static RestResponse<T> GetRestResponse<T>(string restUri)
|
|
where T : new()
|
|
{
|
|
var ctor = new DefaultUriConstructor("http", "issues.umbraco.org", 80, "");
|
|
|
|
var auth = new RestClient();
|
|
auth.RemoveHandler("application/json");
|
|
|
|
var req = new RestRequest(ctor.ConstructBaseUri("user/login"), Method.POST);
|
|
req.AddParameter("login", Login);
|
|
req.AddParameter("password", Password);
|
|
|
|
var resp = auth.Execute(req);
|
|
var cookie = resp.Cookies.ToArray();
|
|
|
|
var getVBundle = new RestRequest(ctor.ConstructBaseUri(restUri));
|
|
foreach (var restResponseCookie in cookie)
|
|
getVBundle.AddCookie(restResponseCookie.Name, restResponseCookie.Value);
|
|
|
|
var gotBundle = auth.Execute<T>(getVBundle);
|
|
return (RestResponse<T>)gotBundle;
|
|
}
|
|
}
|
|
}
|