Ready to beta test

This commit is contained in:
2022-08-26 15:40:05 -07:00
parent 121c0c4d0c
commit b3e643c221
90 changed files with 5023 additions and 20 deletions

View File

@ -0,0 +1,42 @@
@page
@model Mesa_Backlog.Pages.ErrorModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
</head>
<body>
<div class="main">
<div class="content px-4">
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace Mesa_Backlog.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _Logger;
public ErrorModel(ILogger<ErrorModel> logger) => _Logger = logger;
public void OnGet() => RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}

View File

@ -0,0 +1,97 @@
@page
@model Mesa_Backlog.Pages.FetchData
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Mesa Backlog - Data</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" />
<style>
.BugFix {
background-color: #ddd9c4;
}
.High {
background-color: #ffffff;
}
.Med {
background-color: #c4d79b;
}
.Low {
background-color: #fabf8f;
}
.TBD {
background-color: #ffffff;
}
td,
a {
color: #000000;
}
</style>
</head>
<body>
<div class="main">
<div class="content px-4">
<table border="1" style="min-width:900px;">
<tr>
<td>
<a href="@(nameof(Mesa_Backlog.Pages.FetchData))">View (from DevOps)</a><br />
</td>
<td>
@if (!string.IsNullOrEmpty(Model.JSON))
{
<form method="post" action="@(nameof(Mesa_Backlog.Pages.UploadAndExtract))"
asp-antiforgery="true">
<input type="submit" value="Back" /><br />
<input type="hidden" asp-for="@(Model.JSON)" />
</form>
}
</td>
<td>
<a href="@(nameof(Mesa_Backlog.Pages.UploadAndExtract))">Upload and Extract</a>
</td>
<td>
@if (Model.JsonFiles is not null && Model.JsonFiles.Any())
{
<form method="post" asp-antiforgery="true">
<select asp-for="@(Model.JsonFileName)">
@foreach (string jsonFile in Model.JsonFiles)
{
@(await Html.PartialAsync(nameof(Mesa_Backlog.Pages.Shared.FileOption), jsonFile))
}
</select><br />
<input type="submit" value="View last uploaded" />
</form>
}
</td>
</tr>
</table><br />
@if (Model.WorkItems is not null && Model.WorkItems.Any())
{
<h2>@(Model.WorkItems.Count) Work Item(s)</h2>
@(await Html.PartialAsync(nameof(Mesa_Backlog.Pages.Shared.WorkItemsTable), Model.WorkItems))
}
@if (!string.IsNullOrEmpty(Model.JSON))
{
<form method="post" asp-antiforgery="true">
<input type="submit" value="And new to DevOps" /><br />
<textarea cols="150" rows="25" asp-for="@(Model.JSON)">@Html.Raw(Model.JSON)</textarea><br />
<input type="hidden" asp-for="@(Model.PageName)"
value="@(nameof(Mesa_Backlog.Pages.FetchData.PageName))" />
</form>
}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,281 @@
using Mesa_Backlog.Library;
using Mesa_Backlog.Library.WorkItems;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Http = System.Net.Http;
using IO = System.IO;
using WebApi = Microsoft.TeamFoundation.WorkItemTracking.WebApi;
namespace Mesa_Backlog.Pages;
public class FetchData : PageModel
{
[BindProperty]
public string? JSON { get; set; }
[BindProperty]
public string? PageName { get; set; }
[BindProperty]
public string? JsonFileName { get; set; }
public bool AreLoaded { init; get; }
public List<string> JsonFiles { init; get; }
private readonly string _Directory;
private readonly AppSettings _AppSettings;
private readonly bool _IsEnvironmentDevelopment;
private readonly IHostEnvironment _HostEnvironment;
private readonly IHttpClientFactory _HttpClientFactory;
public List<ViewModels.WorkItem> WorkItems { init; get; }
private readonly Dictionary<string, int?> _KeyValuePairs;
private readonly WebApi.WorkItemTrackingHttpClient _WorkItemTrackingHttpClient;
public FetchData(IHostEnvironment hostEnvironment, AppSettings appSettings, IHttpClientFactory httpClientFactory, WebApi.WorkItemTrackingHttpClient workItemTrackingHttpClient, Dictionary<string, int?> keyValuePairs)
{
JsonFiles = new();
WorkItems = new();
_AppSettings = appSettings;
_KeyValuePairs = keyValuePairs;
_HostEnvironment = hostEnvironment;
_HttpClientFactory = httpClientFactory;
_WorkItemTrackingHttpClient = workItemTrackingHttpClient;
_IsEnvironmentDevelopment = hostEnvironment.IsDevelopment();
AreLoaded = (from l in keyValuePairs where l.Value is not null select true).Any();
_Directory = Path.Combine(_HostEnvironment.ContentRootPath, ".vscode", "Uploads");
if (!Directory.Exists(_Directory))
_ = Directory.CreateDirectory(_Directory);
}
private void UpdateKeyValuePairs(FIBacklogMesa[] fIBacklogMesaCollection)
{
foreach (FIBacklogMesa fIBacklogMesa in fIBacklogMesaCollection)
{
if (!_KeyValuePairs.ContainsKey(fIBacklogMesa.Req))
_KeyValuePairs.Add(fIBacklogMesa.Req, null);
}
}
private void SetWorkItems(FIBacklogMesa[] fIBacklogMesaCollection)
{
UpdateKeyValuePairs(fIBacklogMesaCollection);
string project = _AppSettings.Client.Project;
string workItemsAddress = $"{_AppSettings.Client.BaseAddress}{_AppSettings.Client.BasePage}{project}/_workItems/edit/";
List<ViewModels.WorkItem> workItems = ViewModels.WorkItem.GetWorkItems(_KeyValuePairs, workItemsAddress, fIBacklogMesaCollection);
WorkItems.AddRange(workItems);
}
private IActionResult GetPage(string json)
{
IActionResult result;
FIBacklogMesa[] fIBacklogMesaCollection = ExcelReader.GetFIBacklogMesaCollection(json);
SetWorkItems(fIBacklogMesaCollection);
JSON = ExcelReader.GetJson(fIBacklogMesaCollection);
string fileName = Path.Combine(_Directory, $"{DateTime.Now.Ticks}.json");
IO.File.WriteAllText(fileName, JSON);
result = Page();
return result;
}
private IActionResult GetPage(List<string> jsonFiles, string jsonFileName)
{
IActionResult result;
string json = IO.File.ReadAllText(jsonFileName);
FIBacklogMesa[] fIBacklogMesaCollection = ExcelReader.GetFIBacklogMesaCollection(json);
SetWorkItems(fIBacklogMesaCollection);
if (!jsonFiles.Any())
{
jsonFiles.AddRange(Directory.GetFiles(_Directory, "*.json"));
JsonFiles.AddRange(jsonFiles.OrderByDescending(l => l));
}
if (!jsonFiles.Contains(jsonFileName))
{
string fileName = Path.Combine(_Directory, $"{DateTime.Now.Ticks}.json");
IO.File.WriteAllText(fileName, JSON);
}
JSON = json;
result = Page();
return result;
}
private Dictionary<int, string> GetFlopped()
{
Dictionary<int, string> results = new();
foreach (KeyValuePair<string, int?> keyValuePair in _KeyValuePairs)
{
if (keyValuePair.Value is null)
continue;
results.Add(keyValuePair.Value.Value, keyValuePair.Key);
}
return results;
}
private void MergeBack(Dictionary<int, string> keyValuePairs)
{
foreach (KeyValuePair<int, string> keyValuePair in keyValuePairs)
{
if (_KeyValuePairs.ContainsKey(keyValuePair.Value))
_KeyValuePairs[keyValuePair.Value] = keyValuePair.Key;
else
_KeyValuePairs.Add(keyValuePair.Value, keyValuePair.Key);
}
}
private List<Root> GetWorkItems()
{
List<Root> results = new();
Root workItem;
string query = _AppSettings.Client.Query;
using Http.HttpClient httpClient = _HttpClientFactory.CreateClient(nameof(FetchData));
Library.WIQL.WorkItem[] workItems = Library.HttpClient.GetWorkItems(httpClient, _AppSettings.Client.BasePage, _AppSettings.Client.API, query);
for (int i = 0; i < workItems.Length; i++)
{
workItem = Library.HttpClient.GetWorkItem(httpClient, _AppSettings.Client.BasePage, _AppSettings.Client.API, workItems[i].Id);
if (workItem is null)
break;
results.Add(workItem);
}
return results;
}
private void CreateNew(string project, Dictionary<int, string> keyValuePairs, List<FIBacklogMesa> fIBacklogMesaCollection)
{
Task<WebApi.Models.WorkItem> workItemTask;
foreach (FIBacklogMesa fIBacklogMesa in fIBacklogMesaCollection.OrderBy(l => int.Parse(l.Req)))
{
if (string.IsNullOrEmpty(fIBacklogMesa.Title))
continue;
workItemTask = WorkItemTrackingHttpClient.CreateWorkItem(_IsEnvironmentDevelopment, _WorkItemTrackingHttpClient, project, fIBacklogMesa);
workItemTask.Wait();
if (workItemTask.Result is null)
throw new NullReferenceException(nameof(workItemTask.Result));
if (workItemTask.Result?.Id is not null)
keyValuePairs.Add(workItemTask.Result.Id.Value, fIBacklogMesa.Req);
}
}
private void PossiblyCreate(FIBacklogMesa[] fIBacklogMesaCollection, Dictionary<int, string> keyValuePairs)
{
List<FIBacklogMesa> collection = new();
foreach (FIBacklogMesa fIBacklogMesa in fIBacklogMesaCollection)
{
if (string.IsNullOrEmpty(fIBacklogMesa.Title))
continue;
if (_KeyValuePairs.ContainsKey(fIBacklogMesa.Req) && _KeyValuePairs[fIBacklogMesa.Req] is not null)
continue;
collection.Add(fIBacklogMesa);
}
if (collection.Any() && fIBacklogMesaCollection.Length != collection.Count)
CreateNew(_AppSettings.Client.Project, keyValuePairs, collection);
}
private void Update(string project, List<(int, FIBacklogMesa)> collection)
{
Task<WebApi.Models.WorkItem> workItemTask;
foreach ((int id, FIBacklogMesa fIBacklogMesa) in collection)
{
if (string.IsNullOrEmpty(fIBacklogMesa.Title))
continue;
workItemTask = WorkItemTrackingHttpClient.UpdateWorkItem(_WorkItemTrackingHttpClient, project, id, fIBacklogMesa);
workItemTask.Wait();
if (workItemTask.Result is null)
throw new NullReferenceException(nameof(workItemTask.Result));
}
}
private void PossiblyUpdate(FIBacklogMesa[] fIBacklogMesaCollection, List<Root> workItems)
{
int? index;
string title;
FIBacklogMesa fIBacklogMesa;
List<(int, FIBacklogMesa)> collection = new();
foreach (Root workItem in workItems)
{
index = null;
for (int i = 0; i < fIBacklogMesaCollection.Length; i++)
{
fIBacklogMesa = fIBacklogMesaCollection[i];
if (string.IsNullOrEmpty(fIBacklogMesa.Title))
continue;
if (string.IsNullOrEmpty(workItem.Fields.MicrosoftVSTSTCMSystemInfo) || !workItem.Fields.MicrosoftVSTSTCMSystemInfo.Contains(fIBacklogMesa.Req))
continue;
index = i;
break;
}
if (index is null)
continue;
fIBacklogMesa = fIBacklogMesaCollection[index.Value];
title = fIBacklogMesa.Title.Length > 254 ? fIBacklogMesa.Title[..255] : fIBacklogMesa.Title;
if (workItem.Fields.SystemTitle != title)
collection.Add((workItem.Id, fIBacklogMesa));
else if (workItem.Fields.SystemDescription != fIBacklogMesa.Definition)
collection.Add((workItem.Id, fIBacklogMesa));
}
if (collection.Any() && fIBacklogMesaCollection.Length != collection.Count)
Update(_AppSettings.Client.Project, collection);
}
private IActionResult GetPage(string json, string pageName)
{
IActionResult result;
FIBacklogMesa[]? fIBacklogMesaCollection = System.Text.Json.JsonSerializer.Deserialize<FIBacklogMesa[]>(json);
if (fIBacklogMesaCollection is null)
throw new NullReferenceException(nameof(fIBacklogMesaCollection));
UpdateKeyValuePairs(fIBacklogMesaCollection);
List<Root> workItems = GetWorkItems();
if (!workItems.Any())
throw new Exception("Failed to get work items!");
Dictionary<int, string> keyValuePairs = GetFlopped();
PossiblyCreate(fIBacklogMesaCollection, keyValuePairs);
PossiblyUpdate(fIBacklogMesaCollection, workItems);
SetWorkItems(fIBacklogMesaCollection);
MergeBack(keyValuePairs);
result = RedirectToPage($"./{nameof(FetchData)}");
return result;
}
public IActionResult OnPost()
{
IActionResult result;
WorkItems.Clear();
if (!string.IsNullOrEmpty(JsonFileName))
result = GetPage(JsonFiles, JsonFileName);
else if (!string.IsNullOrEmpty(JSON) && string.IsNullOrEmpty(PageName))
result = GetPage(JSON);
else if (!string.IsNullOrEmpty(JSON) && !string.IsNullOrEmpty(PageName))
result = GetPage(JSON, PageName);
else
result = Page();
return result;
}
private IActionResult SetWorkItemsAndGetPage(string json)
{
IActionResult result;
FIBacklogMesa[] fIBacklogMesaCollection = ExcelReader.GetFIBacklogMesaCollection(json);
SetWorkItems(fIBacklogMesaCollection);
result = Page();
return result;
}
public IActionResult OnGet()
{
IActionResult result;
JsonFiles.Clear();
WorkItems.Clear();
string[] jsonFiles = Directory.GetFiles(_Directory, "*.json");
JsonFiles.AddRange(jsonFiles.OrderByDescending(l => l));
if (!string.IsNullOrEmpty(JSON))
result = SetWorkItemsAndGetPage(JSON);
else if (!string.IsNullOrEmpty(JsonFileName) && IO.File.Exists(JsonFileName))
{
string json = IO.File.ReadAllText(JsonFileName);
result = SetWorkItemsAndGetPage(json);
}
else
result = Page();
return result;
}
}

View File

@ -0,0 +1,16 @@
@model string
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@if (!string.IsNullOrEmpty(Model) && File.Exists(Model))
{
string nowTicks = DateTime.Now.Ticks.ToString();
string fileName = System.IO.Path.GetFileNameWithoutExtension(Model);
if (fileName.Length != nowTicks.Length || !long.TryParse(fileName, out long ticks))
{
<option value="@(Model)">@(fileName)</option>
}
else
{
<option value="@(Model)">@(new DateTime(ticks).ToString("MM/dd/yyyy hh:mm tt"))</option>
}
}

View File

@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Mesa_Backlog.Pages.Shared;
public class FileOption : PageModel
{
}

View File

@ -0,0 +1,51 @@
@model List<Mesa_Backlog.ViewModels.WorkItem>
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<table class="table">
<thead>
<tr>
<th>Req<!--Id || Discussion[2]--></th>
<th>Submitted<!--Created Date--></th>
<th>Requestor<!--Assigned To Display Name--></th>
<th>Assigned To<!--Tags--></th>
<th>,<!--Title--></th>
<th>System(s)<!--Area Path--></th>
<th>Priority<!--Priority--></th>
<th>Status<!--Iteration Path--></th>
<th>Definition<!--Description--></th>
<th>Updates<!--Discussion--></th>
<th>Est Effort (days)<!--Effort--></th>
<th>Commit Date<!--TargetDate--></th>
<th>Re-Commit Date<!--&nbsp;--></th>
<th>UAT as of<!--ResolvedDate--></th>
<th>CMP Date<!--ClosedDate--></th>
<th>&nbsp;<!--State--></th>
</tr>
</thead>
<tbody>
@if(Model is not null)
{
@foreach (var workItem in Model)
{
<tr class="@workItem.Priority" id="@(workItem.Id)">
<td><a href="@(workItem.HypertextReference)" target="_blank">@workItem.Req</a></td>
<td>@workItem.Submitted</td>
<td>@workItem.Requestor</td>
<td>@workItem.AssignedTo</td>
<td><a href="@(workItem.HypertextReference)" target="_blank">@workItem.Title</a></td>
<td>@workItem.Systems</td>
<td><a href="@(workItem.HypertextReference)" target="_blank">@workItem.Priority</a></td>
<td><a href="@(workItem.HypertextReference)" target="_blank">@workItem.Status</a></td>
<td>@Html.Raw(workItem.Definition)</td>
<td>@Html.Raw(workItem.Updates)</td>
<td>@workItem.EstEffortDays</td>
<td>@workItem.CommitDate</td>
<td>&nbsp;</td>
<td>@workItem.UATAsOf</td>
<td>@workItem.CMPDate</td>
<td>@workItem.State</td>
</tr>
}
}
</tbody>
</table>

View File

@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Mesa_Backlog.Pages.Shared;
public class WorkItemsTable : PageModel
{
}

View File

@ -0,0 +1,70 @@
@page
@model Mesa_Backlog.Pages.UploadAndExtract
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Mesa Backlog - Upload</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" />
<style>
.BugFix {
background-color: #ddd9c4;
}
.High {
background-color: #ffffff;
}
.Med {
background-color: #c4d79b;
}
.Low {
background-color: #fabf8f;
}
.TBD {
background-color: #ffffff;
}
td,
a {
color: #000000;
}
</style>
</head>
<body>
<div class="main">
<div class="content px-4">
@if (string.IsNullOrEmpty(Model.JSON))
{
<a href="@(nameof(Mesa_Backlog.Pages.FetchData))">View (from DevOps)</a><br />
<a href="@(nameof(Mesa_Backlog.Pages.UploadAndExtract))">Upload and Extract</a>
}
@if (Model.JSON is null || string.IsNullOrEmpty(Model.JSON))
{
<form method="post" enctype="multipart/form-data" asp-antiforgery="true">
<input type="file" asp-for="@(Model.FormFile))" />
<input type="submit" value="Upload and Extract as json" />
</form>
}
else
{
<form method="post" action="@(nameof(Mesa_Backlog.Pages.FetchData))" asp-antiforgery="true">
<input type="submit" value="View as Table" /><br />
<textarea cols="150" rows="25" asp-for="@(Model.JSON)">
@Html.Raw(Model.JSON)
</textarea>
</form>
}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,44 @@
using Mesa_Backlog.Library;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using IO = System.IO;
namespace Mesa_Backlog.Pages;
public class UploadAndExtract : PageModel
{
[BindProperty]
public string? JSON { get; set; }
[BindProperty]
public IFormFile? FormFile { get; set; }
private readonly AppSettings _AppSettings;
private readonly IHostEnvironment _HostEnvironment;
public UploadAndExtract(IHostEnvironment hostEnvironment, AppSettings appSettings)
{
_AppSettings = appSettings;
_HostEnvironment = hostEnvironment;
}
public IActionResult OnPost()
{
string directory = Path.Combine(_HostEnvironment.ContentRootPath, ".vscode", "Uploads");
if (!Directory.Exists(directory))
_ = Directory.CreateDirectory(directory);
if (FormFile is not null)
{
string fileName = Path.Combine(directory, FormFile.FileName);
string copyFileName = Path.Combine(directory, $"{DateTime.Now.Ticks}{Path.GetExtension(fileName)}");
using FileStream fileStream = new(fileName, FileMode.Create);
FormFile.CopyTo(fileStream);
IO.File.Copy(fileName, copyFileName);
JSON = ExcelReader.GetJson(copyFileName, _AppSettings.Excel.Sheet);
IO.File.Delete(copyFileName);
}
return Page();
}
}

View File

@ -0,0 +1,8 @@
@page "/"
@namespace Mesa_Backlog.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_Layout";
}
<component type="typeof(App)" render-mode="ServerPrerendered" />

View File

@ -0,0 +1,32 @@
@using Microsoft.AspNetCore.Components.Web
@namespace Mesa_Backlog.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link href="css/site.css" rel="stylesheet" />
<link href="Mesa-Backlog.styles.css" rel="stylesheet" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
@RenderBody()
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
</body>
</html>