From 0a5d523a865c8c63730e2c6eb6147e1d08e4c63d Mon Sep 17 00:00:00 2001 From: Alex Hedley <79629950+alex-hedley@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:51:27 +0100 Subject: [PATCH 1/3] #219 Original CircleCiList ruby widget --- .../CircleCIList/_/circle_ci_list.coffee | 1 + .../CircleCIList/_/circle_ci_list.html | 12 +++ .../CircleCIList/_/circle_ci_list.rb | 41 +++++++++ .../CircleCIList/_/circle_ci_list.scss | 91 +++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 src/Blashing.Widgets/CircleCIList/_/circle_ci_list.coffee create mode 100644 src/Blashing.Widgets/CircleCIList/_/circle_ci_list.html create mode 100644 src/Blashing.Widgets/CircleCIList/_/circle_ci_list.rb create mode 100644 src/Blashing.Widgets/CircleCIList/_/circle_ci_list.scss diff --git a/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.coffee b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.coffee new file mode 100644 index 0000000..ab7b25d --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.coffee @@ -0,0 +1 @@ +class Dashing.CircleCiList extends Dashing.Widget \ No newline at end of file diff --git a/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.html b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.html new file mode 100644 index 0000000..9310d3f --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.html @@ -0,0 +1,12 @@ +

+ + + + \ No newline at end of file diff --git a/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.rb b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.rb new file mode 100644 index 0000000..ad73f5c --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.rb @@ -0,0 +1,41 @@ +require 'httparty' +require 'digest/md5' + +projects = [ + { user: 'MY_GITHUB_USER_OR_ORG', repo: 'XXX', branch: 'master' }, + { user: 'MY_GITHUB_USER_OR_ORG', repo: 'YYY', branch: 'dev' }, +] + +def translate_status_to_class(status) + statuses = { + 'success' => 'passed', + 'fixed' => 'passed', + 'running' => 'pending', + 'failed' => 'failed' + } + statuses[status] || 'pending' +end + +def update_builds(project, auth_token) + api_url = 'https://circleci.com/api/v1/project/%s/%s/tree/%s?circle-token=%s' + api_url = api_url % [project[:user], project[:repo], project[:branch], auth_token] + api_response = HTTParty.get(api_url, :headers => { "Accept" => "application/json" } ) + api_json = JSON.parse(api_response.body) + return {} if api_json.empty? + + latest_build = api_json.select{ |build| build['status'] != 'queued' }.first + email_hash = Digest::MD5.hexdigest(latest_build['committer_email']) + + data = { + repo: "#{project[:repo]}", + branch: "#{latest_build['branch']}", + widget_class: "#{translate_status_to_class(latest_build['status'])}", + avatar_url: "http://www.gravatar.com/avatar/#{email_hash}" + } + return data +end + +SCHEDULER.every '10s', :first_in => 0 do + items = projects.map{ |p| update_builds(p, ENV['CIRCLE_CI_AUTH_TOKEN']) } + send_event('circle-ci-list', { items: items }) +end \ No newline at end of file diff --git a/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.scss b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.scss new file mode 100644 index 0000000..4aec89f --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/_/circle_ci_list.scss @@ -0,0 +1,91 @@ +$value-color: #FFF; +$background-color: #444; +$background-error-color: #A31F1F; +$background-passed-color: #8fb347; +$background-pending-color: #47bbb3; + +$title-color: rgba(255, 255, 255, 1); +$label-color: rgba(255, 255, 255, 0.7); + +.widget.widget-circle-ci-list{ + + background-color: $background-color; + padding: 0px; + vertical-align: top; + + img.background { + width: 100% !important; + position: absolute; + left: 0; + top: 30px; + opacity: 0.05; + } + + .title { + color: $title-color; + } + + ol, ul { + margin: 0px; + text-align: left; + color: $label-color; + } + + ol { + list-style-position: inside; + } + + li { + margin-bottom: 5px; + } + + .list-nostyle { + } + + .items{ + list-style: none; + + li { + margin-top: 5px; + + &.failed { + background-color: $background-error-color; + } + + &.pending { + background-color: $background-pending-color; + } + + &.passed { + background-color: $background-passed-color; + } + + .label { + display: block; + color: $label-color; + font-size: 20px; + word-wrap: break-word; + + &.repo { + float: left; + text-align: left; + padding: 5px 0px 5px 10px; + } + + &.branch { + float: left; + text-align: left; + padding-top: 10px; + font-size: 14px; + padding: 11px 0px 5px 5px; + } + } + + .avatar { + float: right; + text-align: right; + width: 40px; + } + } + } +} \ No newline at end of file From 8eb21a1d973609bc63c6cf7c54cd30772a8e4da7 Mon Sep 17 00:00:00 2001 From: Alex Hedley <79629950+alex-hedley@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:29:46 +0000 Subject: [PATCH 2/3] wip: Circle CI List --- .../Pages/AdditionalWidgets.razor | 26 +++++ .../Pages/AdditionalWidgets.razor.cs | 32 ++++++ src/Blashing.Shared/_Imports.razor | 1 + ...ircleCIBuildStatusListWidget.stories.razor | 29 +++++ src/Blashing.Stories/_Imports.razor | 3 + .../CircleCIBuildStatusListWidgetTest.cs | 108 ++++++++++++++++++ .../CircleCIBuildStatusListWidget.razor | 19 +++ .../CircleCIBuildStatusListWidget.razor.cs | 26 +++++ .../CircleCIBuildStatusListWidget.razor.css | 78 +++++++++++++ .../CircleCIBuildStatusListWidget.razor.scss | 91 +++++++++++++++ 10 files changed, 413 insertions(+) create mode 100644 src/Blashing.Shared/Pages/AdditionalWidgets.razor.cs create mode 100644 src/Blashing.Stories/Stories/CircleCIBuildStatusListWidget.stories.razor create mode 100644 src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs create mode 100644 src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor create mode 100644 src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.cs create mode 100644 src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.css create mode 100644 src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.scss diff --git a/src/Blashing.Shared/Pages/AdditionalWidgets.razor b/src/Blashing.Shared/Pages/AdditionalWidgets.razor index 519e1ed..a665620 100644 --- a/src/Blashing.Shared/Pages/AdditionalWidgets.razor +++ b/src/Blashing.Shared/Pages/AdditionalWidgets.razor @@ -34,12 +34,25 @@ + +
+
+ +

Circle CI List

+ + + +
+

+ @@ -52,6 +65,7 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Blashing.Shared/Pages/AdditionalWidgets.razor.cs b/src/Blashing.Shared/Pages/AdditionalWidgets.razor.cs new file mode 100644 index 0000000..b7de82a --- /dev/null +++ b/src/Blashing.Shared/Pages/AdditionalWidgets.razor.cs @@ -0,0 +1,32 @@ +using Blashing.Widgets.CircleCIList; + +namespace Blashing.Shared.Pages; + +public partial class AdditionalWidgets +{ + // Circle CI List + List Items { get; set; } = + [ + new CircleCIDetails + { + Repo = "gocardless", + Branch = "dev", + AvatarUrl = "https://gravatar.com/avatar/3184635da54b04bd362f09bad9aced67?s=200&d=robohash", + BackgroundColor = "#a31f1f" + }, + new CircleCIDetails + { + Repo = "dashboard", + Branch = "main", + AvatarUrl = "https://gravatar.com/avatar/3184635da54b04bd362f09bad9aced67?s=200&d=retro", + BackgroundColor = "#47bbb3" + }, + new CircleCIDetails + { + Repo = "payments-client", + Branch = "feature", + AvatarUrl = "https://gravatar.com/avatar/3184635da54b04bd362f09bad9aced67?s=200&d=wavatar", + BackgroundColor = "#8fb347" + } + ]; +} \ No newline at end of file diff --git a/src/Blashing.Shared/_Imports.razor b/src/Blashing.Shared/_Imports.razor index 584fc30..2e5945c 100644 --- a/src/Blashing.Shared/_Imports.razor +++ b/src/Blashing.Shared/_Imports.razor @@ -19,3 +19,4 @@ @using Blashing.Widgets; @using Blashing.Widgets.ServerStatusSquares; @using Blashing.Widgets.CircleCIBuildStatus; +@using Blashing.Widgets.CircleCIList; \ No newline at end of file diff --git a/src/Blashing.Stories/Stories/CircleCIBuildStatusListWidget.stories.razor b/src/Blashing.Stories/Stories/CircleCIBuildStatusListWidget.stories.razor new file mode 100644 index 0000000..a20806b --- /dev/null +++ b/src/Blashing.Stories/Stories/CircleCIBuildStatusListWidget.stories.razor @@ -0,0 +1,29 @@ +@using Blashing.Widgets.CircleCIList + +@attribute [Stories("Example/Circle CI List")] + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Blashing.Stories/_Imports.razor b/src/Blashing.Stories/_Imports.razor index 14600cd..79dea83 100644 --- a/src/Blashing.Stories/_Imports.razor +++ b/src/Blashing.Stories/_Imports.razor @@ -6,9 +6,12 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop + @using BlazingStory.Components @using BlazingStory.Types @using Blashing.Stories @using Blashing.Stories.Shared @using Blashing.Stories.Stories +@using Blashing.Core.Components +@using Blashing.Widgets \ No newline at end of file diff --git a/src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs b/src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs new file mode 100644 index 0000000..dd8e834 --- /dev/null +++ b/src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs @@ -0,0 +1,108 @@ +using Bunit; +using Xunit; + +using Blashing.Widgets.CircleCIList; + +namespace Blashing.Widgets.Tests; + +public class CircleCIBuildStatusListWidgetTest : TestContext +{ + [Fact] + public void CircleCIBuildStatusListWidgetMarkupShouldContainPassedInValues() + { + var title = "Circle CI List Title"; + var items = new CircleCIDetails + { + Repo = "Repo Name", + Branch = "branch", + AvatarUrl = "https://gravatar.com/avatar/3184635da54b04bd362f09bad9aced67?s=200&d=mp", + BackgroundColor = "#a31f1f" + }; + + var cut = RenderComponent(parameters => parameters + .Add(p => p.Title, title) + .Add(p => p.Items, items) + ); + +// var widget = @" +//

@Title

+// +// +// +// "; + // + // var expectedTitleMarkup = $"

{title}

"; + // cut.FindAll("h1")[0].MarkupMatches(expectedTitleMarkup); + // + // var expectedBuildIdMarkup = $"{buildId}"; + // cut.FindAll("span")[0].MarkupMatches(expectedBuildIdMarkup); + // + // var expectedAvatarUrlMarkup = $""; + // cut.FindAll("img")[1].MarkupMatches(expectedAvatarUrlMarkup); + // + // var expectedCommitterNameMarkup = $"{committerName}"; + // cut.FindAll("span")[1].MarkupMatches(expectedCommitterNameMarkup); + // + // var expectedCommitBodyMarkup = $"{commitBody}"; + // cut.FindAll("span")[2].MarkupMatches(expectedCommitBodyMarkup); + // + // var expectedStateMarkup = $"

{state}

"; + // cut.FindAll("h2")[0].MarkupMatches(expectedStateMarkup); + // + // var expectedTimeMarkup = $"{time}"; + // cut.FindAll("span")[3].MarkupMatches(expectedTimeMarkup); + // + // var expectedMoreInfoMarkup = $"

{moreInfo}

"; + // cut.FindAll("p")[0].MarkupMatches(expectedMoreInfoMarkup); + // + // var expectedUpdatedAtMessageMarkup = $"

{updatedAtMessage}

"; + // cut.FindAll("p")[1].MarkupMatches(expectedUpdatedAtMessageMarkup); + // } + + // [Fact] + // public void CircleCIBuildStatusListWidgetShouldContainPassedInValues() + // { + // var title = "Circle CI Build Status Title"; + // var moreInfo = "More Info"; + // var updatedAtMessage = "Updated At Message"; + // var buildId = "Build Id"; + // var avatarUrl = "Avatar Url"; + // var committerName = "Committer Name"; + // var commitBody = "CommitBody"; + // var state = "State"; + // var time = "Time"; + // + // var cut = RenderComponent(parameters => parameters + // .Add(p => p.Title, title) + // .Add(p => p.MoreInfo, moreInfo) + // .Add(p => p.UpdatedAtMessage, updatedAtMessage) + // .Add(p => p.BuildId, buildId) + // .Add(p => p.AvatarUrl, avatarUrl) + // .Add(p => p.CommitterName, committerName) + // .Add(p => p.CommitBody, commitBody) + // .Add(p => p.State, state) + // .Add(p => p.Time, time) + // ); + // + // var circleCIBuildStatusWidget = cut.Instance; + // Assert.Equal(circleCIBuildStatusWidget.Title, title); + // Assert.Equal(circleCIBuildStatusWidget.MoreInfo, moreInfo); + // Assert.Equal(circleCIBuildStatusWidget.UpdatedAtMessage, updatedAtMessage); + // Assert.Equal(circleCIBuildStatusWidget.BuildId, buildId); + // Assert.Equal(circleCIBuildStatusWidget.AvatarUrl, avatarUrl); + // Assert.Equal(circleCIBuildStatusWidget.CommitterName, committerName); + // Assert.Equal(circleCIBuildStatusWidget.CommitBody, commitBody); + // Assert.Equal(circleCIBuildStatusWidget.State, state); + // Assert.Equal(circleCIBuildStatusWidget.Time, time); + // } +} \ No newline at end of file diff --git a/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor new file mode 100644 index 0000000..21840b1 --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor @@ -0,0 +1,19 @@ +@inherits Blashing.Core.Components.BaseWidget; + +
+

@Title

+ + + +
    + @foreach (var item in Items) + { +
  • + @item.Repo + @item.Branch + +
    +
  • + } +
+
\ No newline at end of file diff --git a/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.cs b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.cs new file mode 100644 index 0000000..6a79e26 --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.cs @@ -0,0 +1,26 @@ +using Blashing.Core.Components; +using Microsoft.AspNetCore.Components; + +namespace Blashing.Widgets.CircleCIList; + +public partial class CircleCIBuildStatusListWidget //: BaseWidget +{ + [Parameter] + public string? Title { get; set; } + + [Parameter] public List Items { get; set; } = new(); + + // protected override void OnParametersSet() + // { + // BackgroundColor ??= "#8fb347"; + // } +} + +public record CircleCIDetails +{ + public required string Repo { get; init; } + public required string Branch { get; init; } + public required string AvatarUrl { get; init; } + + public string? BackgroundColor { get; init; } +} \ No newline at end of file diff --git a/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.css b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.css new file mode 100644 index 0000000..7f60056 --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.css @@ -0,0 +1,78 @@ +.widget.widget-circle-ci-list { + background-color: #444; + padding: 0px; + vertical-align: top; +} + +.widget.widget-circle-ci-list img.background { + width: 100% !important; + position: absolute; + left: 0; + top: 30px; + opacity: 0.05; +} + +.widget.widget-circle-ci-list .title { + color: rgba(255, 255, 255, 1); +} + +.widget.widget-circle-ci-list ol, .widget.widget-circle-ci-list ul { + margin: 0px; + text-align: left; + color: rgba(255, 255, 255, 0.7); +} + +.widget.widget-circle-ci-list ol { + list-style-position: inside; +} + +.widget.widget-circle-ci-list li { + margin-bottom: 5px; +} + +.widget.widget-circle-ci-list .items { + list-style: none; +} + +.widget.widget-circle-ci-list .items li { + margin-top: 5px; +} + +.widget.widget-circle-ci-list .items li.failed { + background-color: #a31f1f; +} + +.widget.widget-circle-ci-list .items li.pending { + background-color: #47bbb3; +} + +.widget.widget-circle-ci-list .items li.passed { + background-color: #8fb347; +} + +.widget.widget-circle-ci-list .items li .label { + display: block; + color: rgba(255, 255, 255, 0.7); + font-size: 20px; + word-wrap: break-word; +} + +.widget.widget-circle-ci-list .items li .label.repo { + float: left; + text-align: left; + padding: 5px 0px 5px 10px; +} + +.widget.widget-circle-ci-list .items li .label.branch { + float: left; + text-align: left; + padding-top: 10px; + font-size: 14px; + padding: 11px 0px 5px 5px; +} + +.widget.widget-circle-ci-list .items li .avatar { + float: right; + text-align: right; + width: 40px; +} \ No newline at end of file diff --git a/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.scss b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.scss new file mode 100644 index 0000000..4aec89f --- /dev/null +++ b/src/Blashing.Widgets/CircleCIList/CircleCIBuildStatusListWidget.razor.scss @@ -0,0 +1,91 @@ +$value-color: #FFF; +$background-color: #444; +$background-error-color: #A31F1F; +$background-passed-color: #8fb347; +$background-pending-color: #47bbb3; + +$title-color: rgba(255, 255, 255, 1); +$label-color: rgba(255, 255, 255, 0.7); + +.widget.widget-circle-ci-list{ + + background-color: $background-color; + padding: 0px; + vertical-align: top; + + img.background { + width: 100% !important; + position: absolute; + left: 0; + top: 30px; + opacity: 0.05; + } + + .title { + color: $title-color; + } + + ol, ul { + margin: 0px; + text-align: left; + color: $label-color; + } + + ol { + list-style-position: inside; + } + + li { + margin-bottom: 5px; + } + + .list-nostyle { + } + + .items{ + list-style: none; + + li { + margin-top: 5px; + + &.failed { + background-color: $background-error-color; + } + + &.pending { + background-color: $background-pending-color; + } + + &.passed { + background-color: $background-passed-color; + } + + .label { + display: block; + color: $label-color; + font-size: 20px; + word-wrap: break-word; + + &.repo { + float: left; + text-align: left; + padding: 5px 0px 5px 10px; + } + + &.branch { + float: left; + text-align: left; + padding-top: 10px; + font-size: 14px; + padding: 11px 0px 5px 5px; + } + } + + .avatar { + float: right; + text-align: right; + width: 40px; + } + } + } +} \ No newline at end of file From 71001e47a071e9d50672e9d87952ab1b0d744f38 Mon Sep 17 00:00:00 2001 From: Alex Hedley <79629950+alex-hedley@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:51:33 +0000 Subject: [PATCH 3/3] update for changes to latests bunit ver --- .../CircleCIBuildStatusListWidgetTest.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs b/src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs index dd8e834..ef39030 100644 --- a/src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs +++ b/src/Blashing.Widgets.Tests/CircleCIBuildStatusListWidgetTest.cs @@ -5,25 +5,26 @@ namespace Blashing.Widgets.Tests; -public class CircleCIBuildStatusListWidgetTest : TestContext +public class CircleCIBuildStatusListWidgetTest : BunitContext { [Fact] public void CircleCIBuildStatusListWidgetMarkupShouldContainPassedInValues() { - var title = "Circle CI List Title"; - var items = new CircleCIDetails - { - Repo = "Repo Name", - Branch = "branch", - AvatarUrl = "https://gravatar.com/avatar/3184635da54b04bd362f09bad9aced67?s=200&d=mp", - BackgroundColor = "#a31f1f" - }; + var title = "Circle CI List Title"; + var items = new CircleCIDetails + { + Repo = "Repo Name", + Branch = "branch", + AvatarUrl = "https://gravatar.com/avatar/3184635da54b04bd362f09bad9aced67?s=200&d=mp", + BackgroundColor = "#a31f1f" + }; + + // var cut = Render(parameters => parameters + // .Add(p => p.Title, title) + // .Add(p => p.Items, items) + // ); + } - var cut = RenderComponent(parameters => parameters - .Add(p => p.Title, title) - .Add(p => p.Items, items) - ); - // var widget = @" //

@Title

//