From b2c5b1fc4c41f107bb1addeec5a09844852ebad4 Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Fri, 27 Apr 2018 13:31:36 +0200 Subject: [PATCH 01/10] updated .DotSettings --- BuildsAppReborn.sln.DotSettings | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/BuildsAppReborn.sln.DotSettings b/BuildsAppReborn.sln.DotSettings index f1af933..95b82c8 100644 --- a/BuildsAppReborn.sln.DotSettings +++ b/BuildsAppReborn.sln.DotSettings @@ -90,7 +90,9 @@ ALWAYS_ADD ALWAYS_ADD True + NEVER False + NEVER USE_FOR_VARIABLES_IN_THIS_CLASS 300 False @@ -932,9 +934,14 @@ II.2.12 <HandlesEvent /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="AA_BB" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + True True + True + True + True True True + True True True True From d4e264517a75ba22993e303aa398a5ac9142761a Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Fri, 27 Apr 2018 16:29:29 +0200 Subject: [PATCH 02/10] added loading of builds per pull request --- .../BuildsAppReborn.Access.csproj | 3 ++ .../TFS/Models/TfsPullRequest.cs | 37 ++++++++++++++ .../TFS/TfsBuildProviderBase.cs | 49 ++++++++++++++++++- .../TFS2017/Models/Tfs2017PullRequest.cs | 17 +++++++ .../TFS2017/Tfs2017BuildProvider.cs | 2 +- .../VSTS/Models/VstsPullRequest.cs | 17 +++++++ .../VSTS/VstsBuildProvider.cs | 2 +- .../BuildsAppReborn.Contracts.csproj | 1 + BuildsAppReborn.Contracts/IBuildProvider.cs | 2 + .../Models/IPullRequest.cs | 27 ++++++++++ 10 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 BuildsAppReborn.Access/TFS/Models/TfsPullRequest.cs create mode 100644 BuildsAppReborn.Access/TFS2017/Models/Tfs2017PullRequest.cs create mode 100644 BuildsAppReborn.Access/VSTS/Models/VstsPullRequest.cs create mode 100644 BuildsAppReborn.Contracts/Models/IPullRequest.cs diff --git a/BuildsAppReborn.Access/BuildsAppReborn.Access.csproj b/BuildsAppReborn.Access/BuildsAppReborn.Access.csproj index e7e0a76..a4a043f 100644 --- a/BuildsAppReborn.Access/BuildsAppReborn.Access.csproj +++ b/BuildsAppReborn.Access/BuildsAppReborn.Access.csproj @@ -64,6 +64,7 @@ + @@ -75,12 +76,14 @@ + + diff --git a/BuildsAppReborn.Access/TFS/Models/TfsPullRequest.cs b/BuildsAppReborn.Access/TFS/Models/TfsPullRequest.cs new file mode 100644 index 0000000..0ebcc49 --- /dev/null +++ b/BuildsAppReborn.Access/TFS/Models/TfsPullRequest.cs @@ -0,0 +1,37 @@ +using System; +using BuildsAppReborn.Contracts.Models; +using Newtonsoft.Json; + +namespace BuildsAppReborn.Access.Models +{ + internal abstract class TfsPullRequest : IPullRequest + { + #region Implementation of IPullRequest + + [JsonProperty("createdBy")] + public virtual IUser CreatedBy { get; protected set; } + + [JsonProperty("description")] + public String Description { get; private set; } + + [JsonProperty("pullRequestId")] + public Int32 Id { get; private set; } + + [JsonProperty("mergeStatus")] + public String MergeStatus { get; private set; } + + [JsonProperty("sourceRefName")] + public String Source { get; private set; } + + [JsonProperty("status")] + public String Status { get; private set; } + + [JsonProperty("targetRefName")] + public String Target { get; private set; } + + [JsonProperty("title")] + public String Title { get; private set; } + + #endregion + } +} \ No newline at end of file diff --git a/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs b/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs index 5d371f4..57c4753 100644 --- a/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs +++ b/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs @@ -17,13 +17,14 @@ namespace BuildsAppReborn.Access { - internal abstract class TfsBuildProviderBase : TfsBuildProviderBase, IBuildProvider + internal abstract class TfsBuildProviderBase : TfsBuildProviderBase, IBuildProvider where TBuild : TfsBuild, new() where TBuildDefinition : TfsBuildDefinition, new() where TUser : TfsUser, new() where TSourceVersion : TfsSourceVersion, new() where TArtifact : TfsArtifact, new() where TTestRun : TfsTestRun, new() + where TPullRequest : TfsPullRequest, new() { #region Implementation of IBuildProvider @@ -92,6 +93,52 @@ public virtual async Task>> GetBuilds(IEnumerab throw new Exception($"Error while processing method!"); } + public virtual async Task>>> GetBuildsByPullRequests(BuildMonitorSettings settings) + { + var projectUrl = settings.GetValueStrict(ProjectUrlSettingKey).TrimEnd('/'); + + var requestUrl = $"{projectUrl}/_apis/git/pullrequests?api-version={ApiVersion}"; + var requestResponse = await GetRequestResponse(requestUrl, settings); + if (requestResponse.IsSuccessStatusCode) + { + var dict = new Dictionary>(); + + var result = await requestResponse.Content.ReadAsStringAsync(); + var data = JsonConvert.DeserializeObject>(JObject.Parse(result)["value"].ToString()); + + foreach (var pullRequest in data) + { + var builds = await GetBuildsOfPullRequest(pullRequest, settings); + dict.Add(pullRequest, builds.Data); + + } + + return new DataResponse < IReadOnlyDictionary < IPullRequest, IEnumerable < IBuild >>> { Data = dict, StatusCode = requestResponse.StatusCode }; + } + + throw new Exception($"Error while processing method!"); + } + + private async Task>> GetBuildsOfPullRequest(IPullRequest pullRequest, BuildMonitorSettings settings) + { + var projectUrl = settings.GetValueStrict(ProjectUrlSettingKey).TrimEnd('/'); + var maxBuilds = settings.GetDefaultValueIfNotExists(MaxBuildsPerDefinitionSettingsKey); + + // use fallback value when no value was defined via settings + if (!maxBuilds.HasValue) maxBuilds = 5; + + var requestUrl = $"{projectUrl}/_apis/build/builds?api-version={ApiVersion}&branchName=refs%2Fpull%2F{pullRequest.Id}%2Fmerge&$top={maxBuilds}"; + var requestResponse = await GetRequestResponse(requestUrl, settings); + if (requestResponse.IsSuccessStatusCode) + { + var result = await requestResponse.Content.ReadAsStringAsync(); + var data = JsonConvert.DeserializeObject>(JObject.Parse(result)["value"].ToString()); + + return new DataResponse> { Data = data, StatusCode = requestResponse.StatusCode }; + } + throw new Exception($"Error while processing method!"); + } + #endregion #region Protected Properties diff --git a/BuildsAppReborn.Access/TFS2017/Models/Tfs2017PullRequest.cs b/BuildsAppReborn.Access/TFS2017/Models/Tfs2017PullRequest.cs new file mode 100644 index 0000000..3fc0a32 --- /dev/null +++ b/BuildsAppReborn.Access/TFS2017/Models/Tfs2017PullRequest.cs @@ -0,0 +1,17 @@ +using BuildsAppReborn.Contracts.Models; +using BuildsAppReborn.Infrastructure; +using Newtonsoft.Json; + +namespace BuildsAppReborn.Access.Models +{ + // ReSharper disable once UnusedMember.Global + internal class Tfs2017PullRequest : TfsPullRequest + { + #region Overrides of Base + + [JsonConverter(typeof(InterfaceTypeConverter))] + public override IUser CreatedBy { get; protected set; } + + #endregion + } +} \ No newline at end of file diff --git a/BuildsAppReborn.Access/TFS2017/Tfs2017BuildProvider.cs b/BuildsAppReborn.Access/TFS2017/Tfs2017BuildProvider.cs index 987b99f..f0dd351 100644 --- a/BuildsAppReborn.Access/TFS2017/Tfs2017BuildProvider.cs +++ b/BuildsAppReborn.Access/TFS2017/Tfs2017BuildProvider.cs @@ -9,7 +9,7 @@ namespace BuildsAppReborn.Access { [BuildProviderExport(typeof(IBuildProvider), Id, Name, AuthenticationModes.Default | AuthenticationModes.AccessToken)] [PartCreationPolicy(CreationPolicy.Shared)] - internal class Tfs2017BuildProvider : TfsBuildProviderBase + internal class Tfs2017BuildProvider : TfsBuildProviderBase { #region Overrides of Base diff --git a/BuildsAppReborn.Access/VSTS/Models/VstsPullRequest.cs b/BuildsAppReborn.Access/VSTS/Models/VstsPullRequest.cs new file mode 100644 index 0000000..44d8ad5 --- /dev/null +++ b/BuildsAppReborn.Access/VSTS/Models/VstsPullRequest.cs @@ -0,0 +1,17 @@ +using BuildsAppReborn.Contracts.Models; +using BuildsAppReborn.Infrastructure; +using Newtonsoft.Json; + +namespace BuildsAppReborn.Access.Models +{ + // ReSharper disable once UnusedMember.Global + internal class VstsPullRequest : TfsPullRequest + { + #region Overrides of Base + + [JsonConverter(typeof(InterfaceTypeConverter))] + public override IUser CreatedBy { get; protected set; } + + #endregion + } +} \ No newline at end of file diff --git a/BuildsAppReborn.Access/VSTS/VstsBuildProvider.cs b/BuildsAppReborn.Access/VSTS/VstsBuildProvider.cs index b6dd7fe..952902c 100644 --- a/BuildsAppReborn.Access/VSTS/VstsBuildProvider.cs +++ b/BuildsAppReborn.Access/VSTS/VstsBuildProvider.cs @@ -9,7 +9,7 @@ namespace BuildsAppReborn.Access { [BuildProviderExport(typeof(IBuildProvider), Id, Name, AuthenticationModes.AccessToken)] [PartCreationPolicy(CreationPolicy.Shared)] - internal class VstsBuildProvider : TfsBuildProviderBase + internal class VstsBuildProvider : TfsBuildProviderBase { #region Overrides of Base diff --git a/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj b/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj index 9068db0..131d085 100644 --- a/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj +++ b/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj @@ -72,6 +72,7 @@ + diff --git a/BuildsAppReborn.Contracts/IBuildProvider.cs b/BuildsAppReborn.Contracts/IBuildProvider.cs index c7eb3c7..22c7393 100644 --- a/BuildsAppReborn.Contracts/IBuildProvider.cs +++ b/BuildsAppReborn.Contracts/IBuildProvider.cs @@ -12,6 +12,8 @@ public interface IBuildProvider Task>> GetBuilds(IEnumerable buildDefinitions, BuildMonitorSettings settings); + Task>>> GetBuildsByPullRequests(BuildMonitorSettings settings); + #endregion } } \ No newline at end of file diff --git a/BuildsAppReborn.Contracts/Models/IPullRequest.cs b/BuildsAppReborn.Contracts/Models/IPullRequest.cs new file mode 100644 index 0000000..3a628ce --- /dev/null +++ b/BuildsAppReborn.Contracts/Models/IPullRequest.cs @@ -0,0 +1,27 @@ +using System; + +namespace BuildsAppReborn.Contracts.Models +{ + public interface IPullRequest + { + #region Public Properties + + IUser CreatedBy { get; } + + String Description { get; } + + Int32 Id { get; } + + String MergeStatus { get; } + + String Source { get; } + + String Status { get; } + + String Target { get; } + + String Title { get; } + + #endregion + } +} \ No newline at end of file From 2b1a73ed42b62800161f4188330b8b4f73de2d7b Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Fri, 27 Apr 2018 17:09:43 +0200 Subject: [PATCH 03/10] added setting to choose from build status view style --- .../BuildsAppReborn.Client.csproj | 1 + .../Converter/EnumDescriptionConverter.cs | 47 +++++++++++++++++++ .../Views/GeneralSettingsControl.xaml | 28 +++++++++++ .../BuildsAppReborn.Contracts.csproj | 1 + .../Models/BuildViewStyle.cs | 13 +++++ .../Models/GeneralSettings.cs | 2 + 6 files changed, 92 insertions(+) create mode 100644 BuildsAppReborn.Client/Converter/EnumDescriptionConverter.cs create mode 100644 BuildsAppReborn.Contracts/Models/BuildViewStyle.cs diff --git a/BuildsAppReborn.Client/BuildsAppReborn.Client.csproj b/BuildsAppReborn.Client/BuildsAppReborn.Client.csproj index 8a4c43b..f3736e8 100644 --- a/BuildsAppReborn.Client/BuildsAppReborn.Client.csproj +++ b/BuildsAppReborn.Client/BuildsAppReborn.Client.csproj @@ -156,6 +156,7 @@ + diff --git a/BuildsAppReborn.Client/Converter/EnumDescriptionConverter.cs b/BuildsAppReborn.Client/Converter/EnumDescriptionConverter.cs new file mode 100644 index 0000000..c9cffc6 --- /dev/null +++ b/BuildsAppReborn.Client/Converter/EnumDescriptionConverter.cs @@ -0,0 +1,47 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Windows.Data; + +namespace BuildsAppReborn.Client.Converter +{ + // https://stackoverflow.com/a/3987099 + public class EnumDescriptionConverter : IValueConverter + { + #region Implementation of IValueConverter + + Object IValueConverter.Convert(Object value, Type targetType, Object parameter, CultureInfo culture) + { + var myEnum = (Enum) value; + var description = GetEnumDescription(myEnum); + return description; + } + + Object IValueConverter.ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) + { + return String.Empty; + } + + #endregion + + #region Private Methods + + private String GetEnumDescription(Enum enumObj) + { + var fieldInfo = enumObj.GetType().GetField(enumObj.ToString()); + + var attribArray = fieldInfo.GetCustomAttributes(false); + + if (attribArray.Length == 0) + { + return enumObj.ToString(); + } + + var attrib = attribArray[0] as DescriptionAttribute; + return attrib?.Description; + } + + #endregion + } +} \ No newline at end of file diff --git a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml index a5c14d9..293a76a 100644 --- a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml +++ b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml @@ -5,12 +5,22 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:viewModels="clr-namespace:BuildsAppReborn.Client.ViewModels" + xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:models="clr-namespace:BuildsAppReborn.Contracts.Models;assembly=BuildsAppReborn.Contracts" d:DataContext="{d:DesignInstance Type=viewModels:GeneralSettingsViewModel}" d:DesignHeight="300" d:DesignWidth="300" mc:Ignorable="d"> + + + + + + @@ -22,6 +32,7 @@ + @@ -90,5 +101,22 @@ VerticalAlignment="Bottom" IsChecked="{Binding GeneralSettings.IncludePreReleases}" IsEnabled="False" /> + + + + + + + + + \ No newline at end of file diff --git a/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj b/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj index 131d085..36a8f34 100644 --- a/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj +++ b/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj @@ -65,6 +65,7 @@ + diff --git a/BuildsAppReborn.Contracts/Models/BuildViewStyle.cs b/BuildsAppReborn.Contracts/Models/BuildViewStyle.cs new file mode 100644 index 0000000..2093cc6 --- /dev/null +++ b/BuildsAppReborn.Contracts/Models/BuildViewStyle.cs @@ -0,0 +1,13 @@ +using System.ComponentModel; + +namespace BuildsAppReborn.Contracts.Models +{ + public enum BuildViewStyle + { + [Description("Build Definition")] + GroupByBuildDefinition, + + [Description("Pull Request")] + GroupByPullRequest + } +} \ No newline at end of file diff --git a/BuildsAppReborn.Contracts/Models/GeneralSettings.cs b/BuildsAppReborn.Contracts/Models/GeneralSettings.cs index a6d3c32..30be382 100644 --- a/BuildsAppReborn.Contracts/Models/GeneralSettings.cs +++ b/BuildsAppReborn.Contracts/Models/GeneralSettings.cs @@ -31,6 +31,8 @@ public GeneralSettings() public TimeSpan UpdateCheckInterval { get; set; } + public BuildViewStyle ViewStyle { get; set; } + #endregion } } \ No newline at end of file From 580be00c0c5e4d9bf097a7041498c8fd8caa484f Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Wed, 16 May 2018 19:34:37 +0200 Subject: [PATCH 04/10] changed IBuild to IPullRequest relation --- BuildsAppReborn.Access/TFS/Models/TfsBuild.cs | 3 + .../TFS/TfsBuildProviderBase.cs | 71 ++++++++++++------- BuildsAppReborn.Contracts/IBuildProvider.cs | 2 +- BuildsAppReborn.Contracts/Models/IBuild.cs | 2 + 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/BuildsAppReborn.Access/TFS/Models/TfsBuild.cs b/BuildsAppReborn.Access/TFS/Models/TfsBuild.cs index 4f8e6cd..eae7207 100644 --- a/BuildsAppReborn.Access/TFS/Models/TfsBuild.cs +++ b/BuildsAppReborn.Access/TFS/Models/TfsBuild.cs @@ -37,6 +37,9 @@ public TfsBuild() [JsonProperty("id")] public Int32 Id { get; private set; } + [JsonIgnore] + public IPullRequest PullRequest { get; internal set; } + [JsonIgnore] public BuildStatus Status { diff --git a/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs b/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs index 57c4753..e013734 100644 --- a/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs +++ b/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs @@ -59,7 +59,7 @@ public virtual async Task>> GetBuilds(IEnumerab var buildDefinitionsList = buildDefinitions.ToList(); if (!buildDefinitionsList.Any()) { - return new DataResponse> {Data = Enumerable.Empty(), StatusCode = HttpStatusCode.NoContent}; + return new DataResponse> {Data = Enumerable.Empty(), StatusCode = HttpStatusCode.NoContent}; } var projectUrl = settings.GetValueStrict(ProjectUrlSettingKey).TrimEnd('/'); @@ -87,13 +87,13 @@ public virtual async Task>> GetBuilds(IEnumerab return new DataResponse> {Data = data, StatusCode = requestResponse.StatusCode}; } - return new DataResponse> {Data = Enumerable.Empty(), StatusCode = requestResponse.StatusCode}; + return new DataResponse> {Data = Enumerable.Empty(), StatusCode = requestResponse.StatusCode}; } throw new Exception($"Error while processing method!"); } - public virtual async Task>>> GetBuildsByPullRequests(BuildMonitorSettings settings) + public virtual async Task>> GetBuildsByPullRequests(BuildMonitorSettings settings) { var projectUrl = settings.GetValueStrict(ProjectUrlSettingKey).TrimEnd('/'); @@ -101,41 +101,37 @@ public virtual async Task>(); + var dict = new Dictionary>(); var result = await requestResponse.Content.ReadAsStringAsync(); var data = JsonConvert.DeserializeObject>(JObject.Parse(result)["value"].ToString()); foreach (var pullRequest in data) { - var builds = await GetBuildsOfPullRequest(pullRequest, settings); - dict.Add(pullRequest, builds.Data); - + var buildsResponse = await GetBuildsOfPullRequest(pullRequest, settings); + if (buildsResponse.IsSuccessStatusCode) + { + dict.Add(pullRequest, buildsResponse.Data); + } + else + { + throw new Exception($"Error while processing method!"); + } } - return new DataResponse < IReadOnlyDictionary < IPullRequest, IEnumerable < IBuild >>> { Data = dict, StatusCode = requestResponse.StatusCode }; - } - - throw new Exception($"Error while processing method!"); - } - - private async Task>> GetBuildsOfPullRequest(IPullRequest pullRequest, BuildMonitorSettings settings) - { - var projectUrl = settings.GetValueStrict(ProjectUrlSettingKey).TrimEnd('/'); - var maxBuilds = settings.GetDefaultValueIfNotExists(MaxBuildsPerDefinitionSettingsKey); - // use fallback value when no value was defined via settings - if (!maxBuilds.HasValue) maxBuilds = 5; - - var requestUrl = $"{projectUrl}/_apis/build/builds?api-version={ApiVersion}&branchName=refs%2Fpull%2F{pullRequest.Id}%2Fmerge&$top={maxBuilds}"; - var requestResponse = await GetRequestResponse(requestUrl, settings); - if (requestResponse.IsSuccessStatusCode) - { - var result = await requestResponse.Content.ReadAsStringAsync(); - var data = JsonConvert.DeserializeObject>(JObject.Parse(result)["value"].ToString()); + // sets the relation of the PR to the build object + foreach (var keyValuePair in dict) + { + foreach (var build in keyValuePair.Value) + { + build.PullRequest = keyValuePair.Key; + } + } - return new DataResponse> { Data = data, StatusCode = requestResponse.StatusCode }; + return new DataResponse> {Data = dict.Values.SelectMany(a =>a).ToList(), StatusCode = requestResponse.StatusCode}; } + throw new Exception($"Error while processing method!"); } @@ -200,6 +196,27 @@ private static async Task GetRequestResponse(String request #region Private Methods + private async Task>> GetBuildsOfPullRequest(IPullRequest pullRequest, BuildMonitorSettings settings) + { + var projectUrl = settings.GetValueStrict(ProjectUrlSettingKey).TrimEnd('/'); + var maxBuilds = settings.GetDefaultValueIfNotExists(MaxBuildsPerDefinitionSettingsKey); + + // use fallback value when no value was defined via settings + if (!maxBuilds.HasValue) maxBuilds = 5; + + var requestUrl = $"{projectUrl}/_apis/build/builds?api-version={ApiVersion}&branchName=refs%2Fpull%2F{pullRequest.Id}%2Fmerge&$top={maxBuilds}"; + var requestResponse = await GetRequestResponse(requestUrl, settings); + if (requestResponse.IsSuccessStatusCode) + { + var result = await requestResponse.Content.ReadAsStringAsync(); + var data = JsonConvert.DeserializeObject>(JObject.Parse(result)["value"].ToString()); + + return new DataResponse> {Data = data, StatusCode = requestResponse.StatusCode}; + } + + throw new Exception($"Error while processing method!"); + } + private Tuple GetGitOwnerAndRepo(String gitHubRepoUrl) { if (!String.IsNullOrWhiteSpace(gitHubRepoUrl)) diff --git a/BuildsAppReborn.Contracts/IBuildProvider.cs b/BuildsAppReborn.Contracts/IBuildProvider.cs index 22c7393..97ca64e 100644 --- a/BuildsAppReborn.Contracts/IBuildProvider.cs +++ b/BuildsAppReborn.Contracts/IBuildProvider.cs @@ -12,7 +12,7 @@ public interface IBuildProvider Task>> GetBuilds(IEnumerable buildDefinitions, BuildMonitorSettings settings); - Task>>> GetBuildsByPullRequests(BuildMonitorSettings settings); + Task>> GetBuildsByPullRequests(BuildMonitorSettings settings); #endregion } diff --git a/BuildsAppReborn.Contracts/Models/IBuild.cs b/BuildsAppReborn.Contracts/Models/IBuild.cs index c0128fb..61b7517 100644 --- a/BuildsAppReborn.Contracts/Models/IBuild.cs +++ b/BuildsAppReborn.Contracts/Models/IBuild.cs @@ -18,6 +18,8 @@ public interface IBuild : IObjectItem, IWebItem Int32 Id { get; } + IPullRequest PullRequest { get; } + DateTime QueueDateTime { get; } IUser Requester { get; } From 8ac8e97ca7c7828b3af193b6c1b123ef2b849c43 Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Wed, 16 May 2018 20:40:34 +0200 Subject: [PATCH 05/10] Implemented showing of Builds by PR --- BuildsAppReborn.Access/BuildMonitor.cs | 43 +++++++++-- .../TFS/TfsBuildProviderBase.cs | 22 ++++-- BuildsAppReborn.Client/App.xaml.cs | 2 +- BuildsAppReborn.Client/Cache/BuildCache.cs | 75 ++++++++++++++++--- .../Settings/ServerSettingsViewModel.cs | 2 +- BuildsAppReborn.Contracts.UI/BuildItem.cs | 22 +++++- .../BuildStatusGroup.cs | 16 +++- .../BuildsAppReborn.Contracts.csproj | 2 + .../Extensions/PullRequestEqualityComparer.cs | 31 ++++++++ .../IBuildMonitorAdvanced.cs | 2 +- .../Models/DataResponse.cs | 13 ++++ .../DataResponseUnsuccessfulException.cs | 23 ++++++ 12 files changed, 219 insertions(+), 34 deletions(-) create mode 100644 BuildsAppReborn.Contracts/Extensions/PullRequestEqualityComparer.cs create mode 100644 BuildsAppReborn.Contracts/Models/DataResponseUnsuccessfulException.cs diff --git a/BuildsAppReborn.Access/BuildMonitor.cs b/BuildsAppReborn.Access/BuildMonitor.cs index 61671c9..67ce177 100644 --- a/BuildsAppReborn.Access/BuildMonitor.cs +++ b/BuildsAppReborn.Access/BuildMonitor.cs @@ -23,9 +23,10 @@ internal sealed class BuildMonitor : IBuildMonitorAdvanced #region Constructors [ImportingConstructor] - public BuildMonitor(LazyContainer buildProviders, LazyContainer notificationProviders) + public BuildMonitor(LazyContainer buildProviders, LazyContainer notificationProviders, IEqualityComparer buildDefinitionEqualityComparer) { this.buildProviders = buildProviders; + this.buildDefinitionEqualityComparer = buildDefinitionEqualityComparer; this.notificationProvider = notificationProviders.GetSupportedNotificationProvider(); #pragma warning disable 4014 this.timer.Elapsed += (sender, args) => BeginPollingBuilds(); @@ -36,12 +37,14 @@ public BuildMonitor(LazyContainer buildP #region Implementation of IBuildMonitorAdvanced - public void Start(IEnumerable settings, TimeSpan pollingInterval) + public void Start(IEnumerable settings, GeneralSettings generalSetting, TimeSpan pollingInterval) { Stop(); Initialize(settings); + this.generalSetting = generalSetting; + if (this.providerSettingsGroup.Any()) { this.timer.Interval = pollingInterval.TotalMilliseconds; @@ -55,6 +58,7 @@ public void Stop() { this.timer.Stop(); this.providerSettingsGroup.Clear(); + this.generalSetting = null; OnMonitorStopped(); } @@ -120,19 +124,40 @@ private void OnMonitorStopped() private async Task> PollBuilds(IBuildProvider provider, BuildMonitorSettings settings) { + try { - var builds = await Task.Run(() => provider.GetBuilds(settings.SelectedBuildDefinitions, settings)); - - if (!builds.IsSuccessStatusCode) + var builds = new DataResponse>(); + if (this.generalSetting?.ViewStyle == BuildViewStyle.GroupByPullRequest) { - this.logger.Warn($"Http status code {builds.StatusCode} returned while polling for builds!"); - this.notificationProvider?.ShowMessage("Failure on getting builds", $"Please check the connection for project(s) '{String.Join(", ", settings.SelectedBuildDefinitions.Select(b => b.Project.Name).Distinct())}'. StatusCode was '{builds.StatusCode}'. See log for more details."); + var prBuilds = await provider.GetBuildsByPullRequests(settings); + prBuilds.ThrowIfUnsuccessful(); + + var definitionsInUse = prBuilds.Data.GroupBy(a => a.Definition, build => build, this.buildDefinitionEqualityComparer).Select(a => a.Key); + var unusedDefinitions = settings.SelectedBuildDefinitions.Except(definitionsInUse, this.buildDefinitionEqualityComparer).ToList(); + if (unusedDefinitions.Any()) + { + var defBuilds = await provider.GetBuilds(settings.SelectedBuildDefinitions, settings); + defBuilds.ThrowIfUnsuccessful(); + + return prBuilds.Data.Concat(defBuilds.Data); + } + + return prBuilds.Data; } else { - return builds.Data; + builds = await provider.GetBuilds(settings.SelectedBuildDefinitions, settings); + builds.ThrowIfUnsuccessful(); } + + + return builds.Data; + } + catch (DataResponseUnsuccessfulException ex) + { + this.logger.Warn($"Http status code {ex.StatusCode} returned while polling for builds!"); + this.notificationProvider?.ShowMessage("Failure on getting builds", $"Please check the connection for project(s) '{String.Join(", ", settings.SelectedBuildDefinitions.Select(b => b.Project.Name).Distinct())}'. StatusCode was '{ex.StatusCode}'. See log for more details."); } catch (Exception exception) { @@ -148,6 +173,7 @@ private async Task> PollBuilds(IBuildProvider provider, Buil #region Private Fields private readonly LazyContainer buildProviders; + private readonly IEqualityComparer buildDefinitionEqualityComparer; private Boolean isPolling; @@ -158,6 +184,7 @@ private async Task> PollBuilds(IBuildProvider provider, Buil private readonly Dictionary> providerSettingsGroup = new Dictionary>(); private readonly Timer timer = new Timer(); + private GeneralSettings generalSetting; #endregion } diff --git a/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs b/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs index e013734..2d8d5b3 100644 --- a/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs +++ b/BuildsAppReborn.Access/TFS/TfsBuildProviderBase.cs @@ -77,12 +77,7 @@ public virtual async Task>> GetBuilds(IEnumerab { var result = await requestResponse.Content.ReadAsStringAsync(); var data = JsonConvert.DeserializeObject>(JObject.Parse(result)["value"].ToString()); - data.Select(d => d.Definition).OfType().ToList().ForEach(d => d.BuildSettingsId = settings.UniqueId); - data.Select(d => d.Requester).OfType().ToList().ForEach(a => a.ImageDataLoader = GetImageData(settings, a)); - - await ResolveSourceVersion(data, projectUrl, settings); - await ResolveArtifacts(data, projectUrl, settings); - await ResolveTestRuns(data, projectUrl, settings); + await ResolveAddtionalBuildData(settings, data, projectUrl); return new DataResponse> {Data = data, StatusCode = requestResponse.StatusCode}; } @@ -119,7 +114,6 @@ public virtual async Task>> GetBuildsByPullRequ } } - // sets the relation of the PR to the build object foreach (var keyValuePair in dict) { @@ -129,7 +123,7 @@ public virtual async Task>> GetBuildsByPullRequ } } - return new DataResponse> {Data = dict.Values.SelectMany(a =>a).ToList(), StatusCode = requestResponse.StatusCode}; + return new DataResponse> {Data = dict.Values.SelectMany(a => a).ToList(), StatusCode = requestResponse.StatusCode}; } throw new Exception($"Error while processing method!"); @@ -211,6 +205,8 @@ private async Task>> GetBuildsOfPullRequest(IPu var result = await requestResponse.Content.ReadAsStringAsync(); var data = JsonConvert.DeserializeObject>(JObject.Parse(result)["value"].ToString()); + await ResolveAddtionalBuildData(settings, data, projectUrl); + return new DataResponse> {Data = data, StatusCode = requestResponse.StatusCode}; } @@ -237,6 +233,16 @@ private Tuple GetGitOwnerAndRepo(String gitHubRepoUrl) return null; } + private async Task ResolveAddtionalBuildData(BuildMonitorSettings settings, List data, String projectUrl) + { + data.Select(d => d.Definition).OfType().ToList().ForEach(d => d.BuildSettingsId = settings.UniqueId); + data.Select(d => d.Requester).OfType().ToList().ForEach(a => a.ImageDataLoader = GetImageData(settings, a)); + + await ResolveSourceVersion(data, projectUrl, settings); + await ResolveArtifacts(data, projectUrl, settings); + await ResolveTestRuns(data, projectUrl, settings); + } + private async Task ResolveArtifacts(IEnumerable builds, String projectUrl, BuildMonitorSettings settings) { foreach (var build in builds) diff --git a/BuildsAppReborn.Client/App.xaml.cs b/BuildsAppReborn.Client/App.xaml.cs index 35af695..f091c1d 100644 --- a/BuildsAppReborn.Client/App.xaml.cs +++ b/BuildsAppReborn.Client/App.xaml.cs @@ -52,7 +52,7 @@ protected override void OnStartup(StartupEventArgs e) this.globalSettingsContainer = compositionContainer.GetExportedValue(); this.buildMonitor = compositionContainer.GetExportedValue(); - this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, TimeSpan.FromMinutes(1)); + this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, this.globalSettingsContainer.GeneralSettings, TimeSpan.FromMinutes(1)); this.notifyIcon = (TaskbarIcon) FindResource("NotifyIcon"); if (this.notifyIcon != null) diff --git a/BuildsAppReborn.Client/Cache/BuildCache.cs b/BuildsAppReborn.Client/Cache/BuildCache.cs index bf868ee..cfbf0a0 100644 --- a/BuildsAppReborn.Client/Cache/BuildCache.cs +++ b/BuildsAppReborn.Client/Cache/BuildCache.cs @@ -19,10 +19,12 @@ public class BuildCache : ViewModelBase #region Constructors [ImportingConstructor] - public BuildCache(IBuildMonitorBasic buildMonitor, IEqualityComparer buildDefinitionEqualityComparer) + internal BuildCache(IBuildMonitorBasic buildMonitor, IEqualityComparer buildDefinitionEqualityComparer, IEqualityComparer pullRequstEqualityComparer, GlobalSettingsContainer globalSettingsContainer) { BuildsStatus = new RangeObservableCollection(); this.buildDefinitionEqualityComparer = buildDefinitionEqualityComparer; + this.pullRequstEqualityComparer = pullRequstEqualityComparer; + this.globalSettingsContainer = globalSettingsContainer; buildMonitor.BuildsUpdated += OnBuildsUpdated; buildMonitor.MonitorStopped += (sender, args) => CacheStatus = BuildCacheStatus.NotConfigured; buildMonitor.MonitorStarted += (sender, args) => CacheStatus = BuildCacheStatus.Loading; @@ -82,25 +84,76 @@ protected virtual void OnCacheUpdated() #region Private Methods - private void OnBuildsUpdated(ICollection builds) + private List GroupBuildsByDefinition(IEnumerable builds, ICollection currentBuildsStatus) { - if (!builds.Any()) return; + currentBuildsStatus = currentBuildsStatus.Where(a => a.BuildDefinition != null).OrderBy(a => a.BuildDefinition.Name).ToList(); var buildStatusGroups = new List(); var groupByDefinition = builds.GroupBy(a => a.Definition, build => build, this.buildDefinitionEqualityComparer); foreach (var grp in groupByDefinition) { - var oldStatus = BuildsStatus.SingleOrDefault(a => this.buildDefinitionEqualityComparer.Equals(grp.Key, a.BuildDefinition)); - var newStatus = new BuildStatusGroup(grp.Key, grp.Select(a => new BuildItem(a)).ToList()); + var oldStatus = currentBuildsStatus.SingleOrDefault(a => this.buildDefinitionEqualityComparer.Equals(grp.Key, a.BuildDefinition)); + var newStatus = new BuildStatusGroup(grp.Key, grp.Select(a => new BuildItem(a, BuildViewStyle.GroupByBuildDefinition)).ToList()); - if (oldStatus != null) - { - // ToDo: implement proper update of bound viewmodel objects instead of creating new ones everytime - newStatus.AdditionalInformationShown = oldStatus.AdditionalInformationShown; - } + OnBuildStatusChanged(oldStatus, newStatus); buildStatusGroups.Add(newStatus); } + + return buildStatusGroups; + } + + private List GroupBuildsByPullRequest(List builds, ICollection currentBuildsStatus) + { + currentBuildsStatus = currentBuildsStatus.Where(a => a.PullRequest != null).OrderBy(a => a.PullRequest.Title).ToList(); + + var buildStatusGroups = new List(); + var groupByPr = builds.GroupBy(a => a.PullRequest, build => build, this.pullRequstEqualityComparer); + foreach (var grp in groupByPr) + { + var oldStatus = currentBuildsStatus.SingleOrDefault(a => this.pullRequstEqualityComparer.Equals(grp.Key, a.PullRequest)); + var newStatus = new BuildStatusGroup(grp.Key, grp.Select(a => new BuildItem(a, BuildViewStyle.GroupByPullRequest)).ToList()); + + OnBuildStatusChanged(oldStatus, newStatus); + + buildStatusGroups.Add(newStatus); + } + + return buildStatusGroups; + } + + private void OnBuildStatusChanged(BuildStatusGroup oldStatus, BuildStatusGroup newStatus) + { + if (oldStatus != null) + { + // ToDo: implement proper update of bound viewmodel objects instead of creating new ones everytime + newStatus.AdditionalInformationShown = oldStatus.AdditionalInformationShown; + } + } + + private void OnBuildsUpdated(ICollection builds) + { + if (!builds.Any()) return; + + var buildStatusGroups = new List(); + + if (this.globalSettingsContainer.GeneralSettings.ViewStyle == BuildViewStyle.GroupByBuildDefinition) + { + buildStatusGroups.AddRange(GroupBuildsByDefinition(builds, BuildsStatus)); + } + else if (this.globalSettingsContainer.GeneralSettings.ViewStyle == BuildViewStyle.GroupByPullRequest) + { + var prBuilds = builds.Where(a => a.PullRequest != null).ToList(); + + var groupedBuildsByPullRequest = GroupBuildsByPullRequest(prBuilds, BuildsStatus); + var groupedBuildsByDefinition = GroupBuildsByDefinition(builds.Except(prBuilds), BuildsStatus); + + buildStatusGroups.AddRange(groupedBuildsByPullRequest); + buildStatusGroups.AddRange(groupedBuildsByDefinition); + } + + //buildStatusGroups = buildStatusGroups.OrderByDescending(a => a.CurrentBuild.BuildStartTime).ToList(); + Application.Current.Dispatcher.Invoke(() => { BuildsStatus.Clear(); @@ -161,6 +214,8 @@ private void UpdateCurrentIcon() private readonly IEqualityComparer buildDefinitionEqualityComparer; private BuildCacheStatus cacheStatus; private String currentIcon; + private readonly GlobalSettingsContainer globalSettingsContainer; + private readonly IEqualityComparer pullRequstEqualityComparer; #endregion diff --git a/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs b/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs index 729e46d..a97ed63 100644 --- a/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs +++ b/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs @@ -56,7 +56,7 @@ public void OnSave() { this.globalSettingsContainer.BuildMonitorSettingsContainer = this.buildMonitorSettingsContainer.Clone(); this.globalSettingsContainer.Save(); - this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, TimeSpan.FromMinutes(1)); + this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, this.globalSettingsContainer.GeneralSettings, TimeSpan.FromMinutes(1)); this.buildMonitor.BeginPollingBuilds(); } diff --git a/BuildsAppReborn.Contracts.UI/BuildItem.cs b/BuildsAppReborn.Contracts.UI/BuildItem.cs index 13b6ea4..03b5bfc 100644 --- a/BuildsAppReborn.Contracts.UI/BuildItem.cs +++ b/BuildsAppReborn.Contracts.UI/BuildItem.cs @@ -10,8 +10,9 @@ public class BuildItem : ViewModelBase { #region Constructors - public BuildItem(IBuild build) + public BuildItem(IBuild build, BuildViewStyle viewStyle) { + this.viewStyle = viewStyle; Build = build; } @@ -93,7 +94,18 @@ public String Comment public ITestRun CurrentTestRun => Build?.TestRuns?.FirstOrDefault(); - public String Description => $"{Build.Definition.Name} - {Build.BuildNumber}"; + public String Description + { + get + { + if (this.viewStyle == BuildViewStyle.GroupByPullRequest) + { + return $"{Build.PullRequest.Title} - {Build.BuildNumber}"; + } + + return $"{Build.Definition.Name} - {Build.BuildNumber}"; + } + } public Byte[] RequesterImage => Build?.Requester?.ImageData; @@ -121,5 +133,11 @@ public void Refresh() private Boolean RequesterIsCommitter => Build?.SourceVersion?.Author?.Name == Build?.Requester?.DisplayName; #endregion + + #region Private Fields + + private readonly BuildViewStyle viewStyle; + + #endregion } } \ No newline at end of file diff --git a/BuildsAppReborn.Contracts.UI/BuildStatusGroup.cs b/BuildsAppReborn.Contracts.UI/BuildStatusGroup.cs index 222f859..a3feb02 100644 --- a/BuildsAppReborn.Contracts.UI/BuildStatusGroup.cs +++ b/BuildsAppReborn.Contracts.UI/BuildStatusGroup.cs @@ -10,13 +10,21 @@ public class BuildStatusGroup : ViewModelBase { #region Constructors - public BuildStatusGroup(IBuildDefinition buildDefinition, IEnumerable buildItems) + private BuildStatusGroup(IEnumerable buildItems) { - BuildDefinition = buildDefinition; - AllBuildItems = buildItems?.OrderBy(a => a.Build?.QueueDateTime).ToList(); } + public BuildStatusGroup(IPullRequest pullRequest, IEnumerable buildItems) : this(buildItems) + { + PullRequest = pullRequest; + } + + public BuildStatusGroup(IBuildDefinition buildDefinition, IEnumerable buildItems) : this(buildItems) + { + BuildDefinition = buildDefinition; + } + #endregion #region Public Properties @@ -41,6 +49,8 @@ public Boolean AdditionalInformationShown public List PreviousBuilds => AllBuildItems.Where(a => a != CurrentBuild).ToList(); + public IPullRequest PullRequest { get; } + #endregion #region Private Fields diff --git a/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj b/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj index 36a8f34..8e33452 100644 --- a/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj +++ b/BuildsAppReborn.Contracts/BuildsAppReborn.Contracts.csproj @@ -54,6 +54,7 @@ + @@ -66,6 +67,7 @@ + diff --git a/BuildsAppReborn.Contracts/Extensions/PullRequestEqualityComparer.cs b/BuildsAppReborn.Contracts/Extensions/PullRequestEqualityComparer.cs new file mode 100644 index 0000000..e0a386e --- /dev/null +++ b/BuildsAppReborn.Contracts/Extensions/PullRequestEqualityComparer.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Runtime.CompilerServices; +using BuildsAppReborn.Contracts.Models; + +namespace BuildsAppReborn.Contracts.Extensions +{ + [Export(typeof(IEqualityComparer))] + public class PullRequestEqualityComparer : IEqualityComparer + { + #region Implementation of IEqualityComparer + + public Boolean Equals(IPullRequest x, IPullRequest y) + { + return x.Id == y.Id; + } + + public Int32 GetHashCode(IPullRequest obj) + { + if (obj == null) + { + return RuntimeHelpers.GetHashCode(null); + } + + return obj.Id.GetHashCode(); + } + + #endregion + } +} \ No newline at end of file diff --git a/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs b/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs index 14c74de..7321aa4 100644 --- a/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs +++ b/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs @@ -12,7 +12,7 @@ public interface IBuildMonitorAdvanced : IBuildMonitorBasic { #region Public Methods - void Start(IEnumerable settings, TimeSpan pollingInterval); + void Start(IEnumerable settings, GeneralSettings generalSettings, TimeSpan pollingInterval); void Stop(); diff --git a/BuildsAppReborn.Contracts/Models/DataResponse.cs b/BuildsAppReborn.Contracts/Models/DataResponse.cs index 06fa51d..fcbab77 100644 --- a/BuildsAppReborn.Contracts/Models/DataResponse.cs +++ b/BuildsAppReborn.Contracts/Models/DataResponse.cs @@ -15,6 +15,7 @@ public Boolean IsSuccessStatusCode { if (StatusCode >= HttpStatusCode.OK) return StatusCode <= (HttpStatusCode) 299; + return false; } } @@ -22,5 +23,17 @@ public Boolean IsSuccessStatusCode public HttpStatusCode StatusCode { get; set; } #endregion + + #region Public Methods + + public void ThrowIfUnsuccessful() + { + if (!IsSuccessStatusCode) + { + throw new DataResponseUnsuccessfulException(StatusCode); + } + } + + #endregion } } \ No newline at end of file diff --git a/BuildsAppReborn.Contracts/Models/DataResponseUnsuccessfulException.cs b/BuildsAppReborn.Contracts/Models/DataResponseUnsuccessfulException.cs new file mode 100644 index 0000000..fd5fba6 --- /dev/null +++ b/BuildsAppReborn.Contracts/Models/DataResponseUnsuccessfulException.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; + +namespace BuildsAppReborn.Contracts.Models +{ + public class DataResponseUnsuccessfulException : Exception + { + #region Constructors + + public DataResponseUnsuccessfulException(HttpStatusCode statusCode) + { + StatusCode = statusCode; + } + + #endregion + + #region Public Properties + + public HttpStatusCode StatusCode { get; } + + #endregion + } +} \ No newline at end of file From 6bc338e4b2c8b6a9a2a27f34086ab11649557bac Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Wed, 16 May 2018 20:48:14 +0200 Subject: [PATCH 06/10] Build view is reloaded when settings is changed --- BuildsAppReborn.Access/BuildMonitor.cs | 25 ++++++++----------- BuildsAppReborn.Client/App.xaml.cs | 2 +- .../Settings/GeneralSettingsViewModel.cs | 9 ++++++- .../Settings/ServerSettingsViewModel.cs | 2 +- .../IBuildMonitorAdvanced.cs | 2 +- .../Models/GeneralSettings.cs | 3 +++ 6 files changed, 24 insertions(+), 19 deletions(-) diff --git a/BuildsAppReborn.Access/BuildMonitor.cs b/BuildsAppReborn.Access/BuildMonitor.cs index 67ce177..67f39d9 100644 --- a/BuildsAppReborn.Access/BuildMonitor.cs +++ b/BuildsAppReborn.Access/BuildMonitor.cs @@ -4,13 +4,11 @@ using System.Linq; using System.Threading.Tasks; using System.Timers; - using BuildsAppReborn.Contracts; using BuildsAppReborn.Contracts.Composition; using BuildsAppReborn.Contracts.Models; using BuildsAppReborn.Contracts.UI.Notifications; using BuildsAppReborn.Infrastructure; - using log4net; namespace BuildsAppReborn.Access @@ -37,17 +35,17 @@ public BuildMonitor(LazyContainer buildP #region Implementation of IBuildMonitorAdvanced - public void Start(IEnumerable settings, GeneralSettings generalSetting, TimeSpan pollingInterval) + public void Start(IEnumerable settings, GeneralSettings generalSettingsParam) { Stop(); Initialize(settings); - this.generalSetting = generalSetting; + this.generalSettings = generalSettingsParam; if (this.providerSettingsGroup.Any()) { - this.timer.Interval = pollingInterval.TotalMilliseconds; + this.timer.Interval = this.generalSettings.PollingInterval.TotalMilliseconds; this.timer.Start(); } @@ -58,7 +56,7 @@ public void Stop() { this.timer.Stop(); this.providerSettingsGroup.Clear(); - this.generalSetting = null; + this.generalSettings = null; OnMonitorStopped(); } @@ -124,11 +122,10 @@ private void OnMonitorStopped() private async Task> PollBuilds(IBuildProvider provider, BuildMonitorSettings settings) { - try { var builds = new DataResponse>(); - if (this.generalSetting?.ViewStyle == BuildViewStyle.GroupByPullRequest) + if (this.generalSettings?.ViewStyle == BuildViewStyle.GroupByPullRequest) { var prBuilds = await provider.GetBuildsByPullRequests(settings); prBuilds.ThrowIfUnsuccessful(); @@ -145,12 +142,9 @@ private async Task> PollBuilds(IBuildProvider provider, Buil return prBuilds.Data; } - else - { - builds = await provider.GetBuilds(settings.SelectedBuildDefinitions, settings); - builds.ThrowIfUnsuccessful(); - } + builds = await provider.GetBuilds(settings.SelectedBuildDefinitions, settings); + builds.ThrowIfUnsuccessful(); return builds.Data; } @@ -172,9 +166,11 @@ private async Task> PollBuilds(IBuildProvider provider, Buil #region Private Fields - private readonly LazyContainer buildProviders; private readonly IEqualityComparer buildDefinitionEqualityComparer; + private readonly LazyContainer buildProviders; + private GeneralSettings generalSettings; + private Boolean isPolling; private ILog logger = LogManager.GetLogger(typeof(BuildMonitor)); @@ -184,7 +180,6 @@ private async Task> PollBuilds(IBuildProvider provider, Buil private readonly Dictionary> providerSettingsGroup = new Dictionary>(); private readonly Timer timer = new Timer(); - private GeneralSettings generalSetting; #endregion } diff --git a/BuildsAppReborn.Client/App.xaml.cs b/BuildsAppReborn.Client/App.xaml.cs index f091c1d..5f53c0f 100644 --- a/BuildsAppReborn.Client/App.xaml.cs +++ b/BuildsAppReborn.Client/App.xaml.cs @@ -52,7 +52,7 @@ protected override void OnStartup(StartupEventArgs e) this.globalSettingsContainer = compositionContainer.GetExportedValue(); this.buildMonitor = compositionContainer.GetExportedValue(); - this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, this.globalSettingsContainer.GeneralSettings, TimeSpan.FromMinutes(1)); + this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, this.globalSettingsContainer.GeneralSettings); this.notifyIcon = (TaskbarIcon) FindResource("NotifyIcon"); if (this.notifyIcon != null) diff --git a/BuildsAppReborn.Client/ViewModels/Settings/GeneralSettingsViewModel.cs b/BuildsAppReborn.Client/ViewModels/Settings/GeneralSettingsViewModel.cs index 42b2885..c6b9ef4 100644 --- a/BuildsAppReborn.Client/ViewModels/Settings/GeneralSettingsViewModel.cs +++ b/BuildsAppReborn.Client/ViewModels/Settings/GeneralSettingsViewModel.cs @@ -4,6 +4,7 @@ using System.Reflection; using BuildsAppReborn.Client.Interfaces; +using BuildsAppReborn.Contracts; using BuildsAppReborn.Contracts.Models; using BuildsAppReborn.Infrastructure; @@ -18,11 +19,12 @@ public class GeneralSettingsViewModel : ViewModelBase, ICloseable, ISaveable #region Constructors [ImportingConstructor] - internal GeneralSettingsViewModel(GlobalSettingsContainer globalSettingsContainer, UpdateChecker updateChecker) + internal GeneralSettingsViewModel(GlobalSettingsContainer globalSettingsContainer, UpdateChecker updateChecker, IBuildMonitorAdvanced buildMonitor) { this.globalSettingsContainer = globalSettingsContainer; this.GeneralSettings = this.globalSettingsContainer.GeneralSettings.Clone(); this.updateChecker = updateChecker; + this.buildMonitor = buildMonitor; SaveCommand = new DelegateCommand(OnSave); } @@ -45,6 +47,10 @@ public void OnSave() { this.globalSettingsContainer.GeneralSettings = GeneralSettings.Clone(); this.globalSettingsContainer.Save(); + + this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, this.globalSettingsContainer.GeneralSettings); + this.buildMonitor.BeginPollingBuilds(); + this.updateChecker.Start(); } @@ -76,6 +82,7 @@ public String CurrentAppVersion private readonly GlobalSettingsContainer globalSettingsContainer; private readonly UpdateChecker updateChecker; + private readonly IBuildMonitorAdvanced buildMonitor; #endregion } diff --git a/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs b/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs index a97ed63..11ef741 100644 --- a/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs +++ b/BuildsAppReborn.Client/ViewModels/Settings/ServerSettingsViewModel.cs @@ -56,7 +56,7 @@ public void OnSave() { this.globalSettingsContainer.BuildMonitorSettingsContainer = this.buildMonitorSettingsContainer.Clone(); this.globalSettingsContainer.Save(); - this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, this.globalSettingsContainer.GeneralSettings, TimeSpan.FromMinutes(1)); + this.buildMonitor.Start(this.globalSettingsContainer.BuildMonitorSettingsContainer, this.globalSettingsContainer.GeneralSettings); this.buildMonitor.BeginPollingBuilds(); } diff --git a/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs b/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs index 7321aa4..f18fdd0 100644 --- a/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs +++ b/BuildsAppReborn.Contracts/IBuildMonitorAdvanced.cs @@ -12,7 +12,7 @@ public interface IBuildMonitorAdvanced : IBuildMonitorBasic { #region Public Methods - void Start(IEnumerable settings, GeneralSettings generalSettings, TimeSpan pollingInterval); + void Start(IEnumerable settings, GeneralSettings generalSettings); void Stop(); diff --git a/BuildsAppReborn.Contracts/Models/GeneralSettings.cs b/BuildsAppReborn.Contracts/Models/GeneralSettings.cs index 30be382..f5b55c0 100644 --- a/BuildsAppReborn.Contracts/Models/GeneralSettings.cs +++ b/BuildsAppReborn.Contracts/Models/GeneralSettings.cs @@ -15,6 +15,7 @@ public GeneralSettings() IncludePreReleases = true; UpdateCheckInterval = TimeSpan.FromHours(1); + PollingInterval = TimeSpan.FromMinutes(1); } #endregion @@ -33,6 +34,8 @@ public GeneralSettings() public BuildViewStyle ViewStyle { get; set; } + public TimeSpan PollingInterval { get; set; } + #endregion } } \ No newline at end of file From 66c0c539f5ab2558fef6eff73b4a86e55f708837 Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Wed, 16 May 2018 20:56:22 +0200 Subject: [PATCH 07/10] Added polling interval mock setting --- .../Views/GeneralSettingsControl.xaml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml index 293a76a..b65c239 100644 --- a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml +++ b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml @@ -33,6 +33,7 @@ + @@ -67,13 +68,14 @@ Grid.Column="0" VerticalAlignment="Bottom" Text="Check interval (minutes)" /> + + Converter={StaticResource MinutesToTimeSpanConverter}, UpdateSourceTrigger=PropertyChanged}" /> + + + + \ No newline at end of file From 57c4ca2508003f583d21f40b684e4db2210ee053 Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Wed, 16 May 2018 20:57:37 +0200 Subject: [PATCH 08/10] Changed settings order --- BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml.cs | 2 +- BuildsAppReborn.Client/Views/ServerSettingsControl.xaml.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml.cs b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml.cs index 994cf4f..d9227de 100644 --- a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml.cs +++ b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml.cs @@ -20,7 +20,7 @@ public GeneralSettingsControl() #region Implementation of ISubSettingsControl - public UInt32 Order => 1; + public UInt32 Order => 0; public String Title => "General Settings"; diff --git a/BuildsAppReborn.Client/Views/ServerSettingsControl.xaml.cs b/BuildsAppReborn.Client/Views/ServerSettingsControl.xaml.cs index ea46c83..9d6d4c3 100644 --- a/BuildsAppReborn.Client/Views/ServerSettingsControl.xaml.cs +++ b/BuildsAppReborn.Client/Views/ServerSettingsControl.xaml.cs @@ -22,7 +22,7 @@ public ServerSettingsControl() public String Title => "Server Settings"; - public UInt32 Order => 0; + public UInt32 Order => 1; #endregion From 7c801e098a7a1aef3d01a41aee2e5f66ec9d54ea Mon Sep 17 00:00:00 2001 From: Luka Grabarevic Date: Wed, 16 May 2018 21:14:32 +0200 Subject: [PATCH 09/10] fixed deselection issue in settings view --- .../Views/GeneralSettingsControl.xaml | 7 +++- .../BuildsAppReborn.Contracts.UI.csproj | 11 +++++ .../DeselectionPreventionBehavior.cs | 41 +++++++++++++++++++ BuildsAppReborn.Contracts.UI/packages.config | 4 ++ 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 BuildsAppReborn.Contracts.UI/DeselectionPreventionBehavior.cs create mode 100644 BuildsAppReborn.Contracts.UI/packages.config diff --git a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml index b65c239..62679a8 100644 --- a/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml +++ b/BuildsAppReborn.Client/Views/GeneralSettingsControl.xaml @@ -7,6 +7,8 @@ xmlns:viewModels="clr-namespace:BuildsAppReborn.Client.ViewModels" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:models="clr-namespace:BuildsAppReborn.Contracts.Models;assembly=BuildsAppReborn.Contracts" + xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" + xmlns:ui="clr-namespace:BuildsAppReborn.Contracts.UI;assembly=BuildsAppReborn.Contracts.UI" d:DataContext="{d:DesignInstance Type=viewModels:GeneralSettingsViewModel}" d:DesignHeight="300" d:DesignWidth="300" @@ -112,13 +114,16 @@ Grid.Column="2" SelectionMode="Single" Style="{DynamicResource MaterialDesignToolToggleFlatListBox}" ItemsSource="{Binding Source={StaticResource BuildViewStyleEnum}}" - SelectedItem="{Binding GeneralSettings.ViewStyle}" + SelectedItem="{Binding GeneralSettings.ViewStyle, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Bottom"> + + + 4 + + ..\packages\MahApps.Metro.1.5.0\lib\net45\MahApps.Metro.dll + + + + ..\packages\MahApps.Metro.1.5.0\lib\net45\System.Windows.Interactivity.dll + @@ -41,6 +48,7 @@ + @@ -57,6 +65,9 @@ BuildsAppReborn.Infrastructure + + +