Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions BuildRadiator/BuildRadiator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<IISExpressUseClassicPipelineMode />
<TargetFrameworkProfile />
<UseGlobalApplicationHostFile />
<Use64BitIISExpress />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -144,12 +145,14 @@
<Content Include="Client\app.js" />
<Content Include="Client\Controllers\LoginController.js" />
<Content Include="Client\Controllers\DashboardController.js" />
<Content Include="Client\Directives\BuildStatistics.js" />
<Content Include="Client\Directives\Clock.js" />
<Content Include="Client\Directives\BuildStatusIcon.js" />
<Content Include="Client\Directives\Gravatar.js" />
<Content Include="Client\Filters\fromNow.js" />
<Content Include="Client\Filters\fromTime.js" />
<Content Include="Client\Services\BuildHub.js" />
<Content Include="Client\Services\BuildStatisticsHub.js" />
<Content Include="Client\Services\MessageHub.js" />
<Content Include="Client\Services\TileConfiguration.js" />
<Content Include="Content\angular-material.css" />
Expand Down Expand Up @@ -197,14 +200,20 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\BuildComparer.cs" />
<Compile Include="Helpers\BuildStatisticsParser.cs" />
<Compile Include="Helpers\BuildStatisticsService.cs" />
<Compile Include="Helpers\BuildService.cs" />
<Compile Include="Helpers\DateTimeExtensions.cs" />
<Compile Include="Helpers\MessageService.cs" />
<Compile Include="Helpers\SignalRJsonSerializer.cs" />
<Compile Include="Hubs\BuildStatisticsHub.cs" />
<Compile Include="Hubs\IBuildHubClient.cs" />
<Compile Include="Hubs\IMessageHubClient.cs" />
<Compile Include="Hubs\MessageHub.cs" />
<Compile Include="Hubs\BuildHub.cs" />
<Compile Include="Model\Builds\BuildStatistics.cs" />
<Compile Include="Model\Tiles\BuildStatisticsTile.cs" />
<Compile Include="Model\Tiles\BuildStatisticsTileConfig.cs" />
<Compile Include="Model\Tiles\ClockTileConfig.cs" />
<Compile Include="Model\Tiles\ClockTile.cs" />
<Compile Include="Controllers\HomeController.cs" />
Expand Down
33 changes: 32 additions & 1 deletion BuildRadiator/Client/Controllers/DashboardController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

var module = angular.module( 'BuildRadiator' );

module.controller( 'DashboardController', ['$scope', '$log', '$sce', 'TileConfiguration', 'BuildHub', 'MessageHub', function( $scope, $log, $sce, TileConfiguration, BuildHub, MessageHub ) {
module.controller( 'DashboardController', ['$scope', '$log', '$sce', 'TileConfiguration', 'BuildHub', 'BuildStatisticsHub', 'MessageHub', function( $scope, $log, $sce, TileConfiguration, BuildHub, BuildStatisticsHub, MessageHub ) {
var self = this;

self.committerLimit = 11;
Expand All @@ -23,6 +23,25 @@
tile.message = message;
};

function onBuildStatisticsUpdate( buildDetails ) {
var tile = self.tiles.find( function( tile ) {
return tile.type === 'build-statistics'
&& tile.config.buildName === buildDetails.name
&& tile.config.branchName === buildDetails.branchName;
} );

if ( !tile ) {
$log.error( 'UNKNOWN PROJECT', buildDetails );
return;
}

if (! tile.buildDetails) {
tile.buildDetails = buildDetails;
}

tile.buildDetails.statistics = buildDetails.statistics;
}

function onProjectUpdateError( build ) {
var tile = self.tiles.find( function( tile ) {
return tile.type === 'project'
Expand Down Expand Up @@ -54,6 +73,14 @@
delete tile.error;
};

function registerBuildStatistics() {
self.tiles.filter( function( t ) {
return t.type === 'build-statistics';
} ).forEach( function( tile ) {
BuildStatisticsHub.server.register( tile.config.buildName, tile.config.branchName );
} );
}

function registerProjects() {
self.tiles.filter( function( t ) {
return t.type === 'project';
Expand All @@ -78,6 +105,10 @@
updateError: onProjectUpdateError
} ).done( registerProjects );

BuildStatisticsHub.connect( $scope, {
update: onBuildStatisticsUpdate
} ).done( registerBuildStatistics );

MessageHub.connect( $scope, {
update: onMessageUpdate
} ).done( registerMessages );
Expand Down
53 changes: 53 additions & 0 deletions BuildRadiator/Client/Directives/BuildStatistics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
( function() {
'use strict';

var module = angular.module( 'BuildRadiator' );

module.directive( 'buildStatistics', ['$interval', function() {
return {
restrict: 'E',
scope: {
buildDetails: '='
},
template: '<div><div>{{ getLast().statementCodeCoverage }}%</div><div class="small {{ getChangeClass() }}">{{ getChangeText() }}</div></div>',
link: function( scope ) {
scope.getLast = function() {
if ( !scope.buildDetails ) {
return null;
}

return scope.buildDetails.statistics[scope.buildDetails.statistics.length - 1];
}

scope.getChangeText = function() {
if ( !scope.buildDetails || scope.buildDetails.statistics.length < 2 ) {
return '';
}

var change = getLastChange();
return ( change >= 0 ? '+' : '' ) + change + '%';
}

scope.getChangeClass = function() {
if ( !scope.buildDetails || scope.buildDetails.statistics.length < 2 ) {
return '';
}

var change = getLastChange();
return change >= 0 ? 'positive-change' : 'negative-change';
}

function getLastChange() {
var statistics = scope.buildDetails.statistics;
if ( statistics.length < 2 ) {
return 0;
}

return parseFloat( ( statistics[statistics.length - 1].statementCodeCoverage -
statistics[statistics.length - 2].statementCodeCoverage ).toFixed( 1 ) );
}
}
};
}] );

} )();
9 changes: 9 additions & 0 deletions BuildRadiator/Client/Services/BuildStatisticsHub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
( function() {
'use strict';

var module = angular.module( 'BuildRadiator' );

module.service( 'BuildStatisticsHub', ['ServerConnection', function( ServerConnection ) {
return ServerConnection( 'buildStatisticsHub' );
}] );
} )();
19 changes: 19 additions & 0 deletions BuildRadiator/Content/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@
font-size: 48px;
}

.build-statistics-tile {
text-align: center;
background: lightblue;
font-size: 48px;
color: black;
}

.build-statistics-content {
margin-top: -24px;
}

.positive-change {
color: darkgreen;
}

.negative-change {
color: darkred;
}

.clock-tile md-grid-tile-footer {
color: rgba(0, 0, 0, 0.87);
}
Expand Down
3 changes: 2 additions & 1 deletion BuildRadiator/Controllers/TileController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public class TileController: ApiController {

static TileController() {
Tiles = new Tile[] {
new ProjectTile( "Ace (master)", "Ace Commit" ) { ColumnSpan = 2, RowSpan = 1 },
new ProjectTile( "Ace (master)", "Ace Commit" )/* { ColumnSpan = 2, RowSpan = 1 }*/,
new BuildStatisticsTile( "Ace Coverage", "Ace Commit" ),
new MessageTile( "Current Theme", "sprintTheme", "fancy" ) { ColumnSpan = 2 },
new ClockTile( "UK Time", "Europe/London" ),
new ProjectTile( "Ngyn", "Ngyn Commit" ),
Expand Down
29 changes: 29 additions & 0 deletions BuildRadiator/Helpers/BuildStatisticsParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Xml;
using Configit.BuildRadiator.Model.Builds;

namespace Configit.BuildRadiator.Helpers {
public class BuildStatisticsParser {
private readonly XmlNode _properties;

public BuildStatisticsParser( XmlNode buildInfo ) {
if ( buildInfo == null ) {
throw new ArgumentNullException( nameof( buildInfo ) );
}

_properties = buildInfo.SelectSingleNode( "./properties" );

if ( _properties == null ) {
throw new Exception( "Invalid xml" );
}
}

public BuildStatistics Parse() {
var classCoverage = Math.Round( double.Parse( _properties.SelectSingleNode("property[@name='CodeCoverageC']").Attributes["value"].InnerText ), 1 );
var methodCoverage = Math.Round( double.Parse( _properties.SelectSingleNode("property[@name='CodeCoverageM']").Attributes["value"].InnerText ), 1 );
var statementCoverage = Math.Round( double.Parse( _properties.SelectSingleNode("property[@name='CodeCoverageS']").Attributes["value"].InnerText ), 1 );

return new BuildStatistics( classCoverage, methodCoverage, statementCoverage );
}
}
}
86 changes: 86 additions & 0 deletions BuildRadiator/Helpers/BuildStatisticsService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Xml;
using Configit.BuildRadiator.Model.Builds;

namespace Configit.BuildRadiator.Helpers {
public class BuildStatisticsService {
public const string DefaultBranchName = "<default>";

private readonly string _baseUrl;
private readonly string _authenticationHeader;

private static string _lastCheckDate = string.Empty;
private static List<BuildStatistics> _allBuildStatistics;

public BuildStatisticsService( string baseUrl, string authenticationHeader ) {
_baseUrl = baseUrl.TrimEnd( '/' );
_authenticationHeader = authenticationHeader;
_allBuildStatistics = new List<BuildStatistics>();
}

private HttpClient CreateClient() {
var client = new HttpClient {
BaseAddress = new Uri( $"{_baseUrl}/httpAuth/app/rest/builds/id:151762/" ) // latest
};

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", _authenticationHeader );

return client;
}

public async Task<BuildDetails> Get( string buildType, string branchName = DefaultBranchName ) {
var client = CreateClient();

var checkDate = DateTime.Now.ToShortDateString();

if ( checkDate != _lastCheckDate ) {
var buildStatisticsTask = Task.Run( () => GetStatistics( client, buildType, branchName ) );

await Task.WhenAll( buildStatisticsTask );

var parser = new BuildStatisticsParser( buildStatisticsTask.Result );

var buildStatistics = parser.Parse();

_allBuildStatistics.Add( buildStatistics );
if ( _allBuildStatistics.Count > 5 ) {
_allBuildStatistics.RemoveAt( 0 );
}

_lastCheckDate = checkDate;
}

return new BuildDetails( buildType, branchName, _allBuildStatistics );
}

private static async Task<XmlDocument> GetStatistics( HttpClient client, string buildType, string branchName ) {
var url = "statistics";//"/buildType:name:" + Uri.EscapeDataString( buildType ) + ",running:any,branch:" + Uri.EscapeUriString( branchName ) + ",count:1?fields=defaultBranch,buildType,branchName,status,statusText,startDate,finishDate,running,running-info";
var cacheBuster = "?t=" + DateTime.UtcNow.Ticks;

var data = await client.GetStringAsync( url + cacheBuster );

var document = new XmlDocument();
document.LoadXml( data );

return document;
}

public class BuildDetails {
public string Name { get; }
public string BranchName { get; }
public List<BuildStatistics> Statistics { get; }

public BuildDetails( string name, string branchName, List<BuildStatistics> statistics) {
Name = name;
BranchName = branchName;
Statistics = statistics;
}
}
}
}
58 changes: 58 additions & 0 deletions BuildRadiator/Hubs/BuildStatisticsHub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using Configit.BuildRadiator.Helpers;
using Microsoft.AspNet.SignalR;

namespace Configit.BuildRadiator.Hubs {
public class BuildStatisticsHub : Hub {
private static readonly TimeSpan RefreshTimer = TimeSpan.FromHours( 1 );
private static readonly BuildStatisticsService BuildStatisticsService;
private string _branchName = "";
private string _projectName = "";

static BuildStatisticsHub() {
var authHeader = EncodeBase64( ConfigurationManager.AppSettings["TeamCityUser"] + ":" + ConfigurationManager.AppSettings["TeamCityPassword"] );
BuildStatisticsService = new BuildStatisticsService( ConfigurationManager.AppSettings["TeamCityUrl"], authHeader );

var timer = new Timer( RefreshTimer.TotalMilliseconds );
timer.Elapsed += TimerOnElapsed;
timer.Start();
}

private static void TimerOnElapsed( object sender, ElapsedEventArgs e ) {
UpdateClients();
}

private static void UpdateClients() {
var context = GlobalHost.ConnectionManager.GetHubContext<BuildStatisticsHub>();
Task.Run( async () => {
var statistics = await BuildStatisticsService.Get( "Ace Commit" );
context.Clients.All.Update( statistics );
} );
}

public void Register( string projectName, string branchName ) {
_projectName = projectName;
_branchName = branchName;

/*
var groupName = BuildGroupName( projectName, branchName );
Groups.Add( Context.ConnectionId, groupName );

var groupTuple = Tuple.Create( projectName, branchName );
GroupCounts.AddOrUpdate( groupTuple, key => 1, ( key, value ) => value + 1 );
RefreshProject( groupTuple, true );
*/

UpdateClients();
}

private static string EncodeBase64( string value ) {
var byteArray = value.ToCharArray().Select( c => (byte)c );
return Convert.ToBase64String( byteArray.ToArray() );
}
}
}
Loading