diff --git a/OurUmbraco.Client/package.json b/OurUmbraco.Client/package.json index 35607df5..59960851 100644 --- a/OurUmbraco.Client/package.json +++ b/OurUmbraco.Client/package.json @@ -11,7 +11,7 @@ "gulp-clean": "0.3.x", "gulp-combine-media-queries": "0.2.x", "gulp-concat": "2.2.x", - "gulp-imagemin": "0.6.x", + "gulp-imagemin": "3.3.x", "gulp-jshint": "^2.0.4", "gulp-minify-css": "1.1.x", "gulp-rename": "1.2.x", diff --git a/OurUmbraco.Client/src/scss/sections/_contributors.scss b/OurUmbraco.Client/src/scss/sections/_contributors.scss new file mode 100644 index 00000000..0ff0f496 --- /dev/null +++ b/OurUmbraco.Client/src/scss/sections/_contributors.scss @@ -0,0 +1,94 @@ +#github-contributors { + &:not(.all) { + .contributor { + &:nth-child(n+17) { + display: none; + } + + @media (max-width: $md) { + &:nth-child(n+13) { + display: none; + } + } + + @media (max-width: $xs) { + &:nth-child(n+10) { + display: none; + } + } + } + } + + &.all { + .button { + display: none; + } + } + + .loadmore { + margin: 20px auto; + width: 100%; + text-align: center; + } +} + +.github-contributors small.link-list { + font-style: italic; + font-weight: normal; + text-transform: none; +} + +.contributor { + margin-bottom: 15px; + + .avatar { + position: relative; + flex: 0 0 23%; + margin-right: 20px; + + @media (min-width: $md) { + flex: 0 0 15%; + } + + img { + border: 3px solid #98d768; + width: 100%; + max-width: 112px; + height: auto; + } + } + + .counts { + position: absolute; + top: 2px; + left: 2px; + right: 2px; + text-align: center; + color: #fff; + font-size: 12px; + line-height: 12px; + background: rgba(0,0,0,0.25); + border-radius: 50%; + bottom: 6px; + padding-top: 23px; + opacity: 0; + transition: opacity 0.25s; + } + + &:hover .counts { + opacity: 1; + } + + .contrib-count { + position: absolute; + left: 25px; + bottom: 0px; + text-align: center; + right: 25px; + color: #425a2e; + font-weight: bold; + background: darken($color-our, 4%); + border-radius: 15px; + z-index: 5; + } +} diff --git a/OurUmbraco.Client/src/scss/style.scss b/OurUmbraco.Client/src/scss/style.scss index c9b8564e..9c396f20 100644 --- a/OurUmbraco.Client/src/scss/style.scss +++ b/OurUmbraco.Client/src/scss/style.scss @@ -60,7 +60,7 @@ @import 'sections/forum'; @import 'sections/people'; @import 'sections/events'; - +@import 'sections/contributors'; /** /* Pages diff --git a/OurUmbraco.Site/OurUmbraco.Site.csproj b/OurUmbraco.Site/OurUmbraco.Site.csproj index 28382206..1d6cdd41 100644 --- a/OurUmbraco.Site/OurUmbraco.Site.csproj +++ b/OurUmbraco.Site/OurUmbraco.Site.csproj @@ -476,6 +476,7 @@ Designer + @@ -4343,6 +4344,7 @@ + web.config diff --git a/OurUmbraco.Site/Views/Partials/Community/Home.cshtml b/OurUmbraco.Site/Views/Partials/Community/Home.cshtml index 10313f39..3a562b7d 100644 --- a/OurUmbraco.Site/Views/Partials/Community/Home.cshtml +++ b/OurUmbraco.Site/Views/Partials/Community/Home.cshtml @@ -1,6 +1,6 @@ @using OurUmbraco.Our.Models @using Skybrud.Social.Meetup.Models.Events -@inherits OurUmbracoTemplatePage +@inherits OurUmbracoTemplatePage @{ var homeNotificationText = Model.Content.GetPropertyValue("homeOnlyBanner"); var showHomeNotification = string.IsNullOrWhiteSpace(homeNotificationText) == false; @@ -40,7 +40,7 @@ @@ -61,6 +61,7 @@ { @ForumActivity() @Meetups() + @GitHubContributors() @TwitterSearch() @Html.Partial("home/documentation") } @@ -69,6 +70,7 @@ else @Html.Partial("home/documentation") @ForumActivity() @Meetups() + @GitHubContributors() @TwitterSearch() } @@ -130,6 +132,43 @@ else } +@helper GitHubContributors() +{ +
+
+
+
+

GitHub Contributions

+

+ Contributions to Umbraco GitHub repositories + + Contributions to default branches for + Umbraco-CMS, + UmbracoDocs, + OurUmbraco, + Umbraco.Deploy.Contrib, + Umbraco.Courier.Contrib and + Umbraco.Deploy.ValueConnectors repos + +

+
+
+ GitHub contributors +
+ +
+

Loading GitHub contributors...

+
+ + + +
+
+
+} + @helper Meetups() {
diff --git a/OurUmbraco.Site/Views/Partials/Community/Scripts.cshtml b/OurUmbraco.Site/Views/Partials/Community/Scripts.cshtml index 60303a44..5a254870 100644 --- a/OurUmbraco.Site/Views/Partials/Community/Scripts.cshtml +++ b/OurUmbraco.Site/Views/Partials/Community/Scripts.cshtml @@ -1,9 +1,14 @@  \ No newline at end of file diff --git a/OurUmbraco.Site/Views/Partials/Home/GitHubContributors.cshtml b/OurUmbraco.Site/Views/Partials/Home/GitHubContributors.cshtml new file mode 100644 index 00000000..046f20d8 --- /dev/null +++ b/OurUmbraco.Site/Views/Partials/Home/GitHubContributors.cshtml @@ -0,0 +1,35 @@ +@model OurUmbraco.Community.Models.GitHubContributorsModel + +@if (Model.Contributors.Any()) +{ + foreach (var contributor in Model.Contributors) + { + var author = contributor.Author; + if (author != null) + { + +
+ @author.Login + + @contributor.TotalCommits + +
+ Additions
+ @contributor.TotalAdditions
+
+ Deletions
+ @contributor.TotalDeletions +
+
+
+ } + } +
+ Load more +
+} + +else +{ +

Could not load recent GitHub contributors.

+} \ No newline at end of file diff --git a/OurUmbraco.Site/config/githubhq.txt b/OurUmbraco.Site/config/githubhq.txt new file mode 100644 index 00000000..63a5edc7 --- /dev/null +++ b/OurUmbraco.Site/config/githubhq.txt @@ -0,0 +1,16 @@ +Aaen +clausjensen +emilwangaa +hartvig +hemraker +madsrasmussen +mikkelhm +nul800sebastiaan +perploug +Shazwazza +simonbusborg +sitereactor +sofietoft +umbracoci +warrenbuckley +zpqrtbnk \ No newline at end of file diff --git a/OurUmbraco/Community/Controllers/GitHubContributorController.cs b/OurUmbraco/Community/Controllers/GitHubContributorController.cs new file mode 100644 index 00000000..4f6d86ea --- /dev/null +++ b/OurUmbraco/Community/Controllers/GitHubContributorController.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Web.Mvc; +using OurUmbraco.Community.Models; +using OurUmbraco.Our.Api; +using RestSharp; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Web.Mvc; + +namespace OurUmbraco.Community.Controllers +{ + public class GitHubContributorController : SurfaceController + { + /// + /// Repositories to include in the combination of contributions + /// + private readonly string[] Repositories = + { + "Umbraco-CMS", + "UmbracoDocs", + "OurUmbraco", + "Umbraco.Deploy.Contrib", + "Umbraco.Courier.Contrib", + "Umbraco.Deploy.ValueConnectors" + }; + + /// + /// Gets data for all GitHub contributors for all listed Umbraco repositories, + /// excluding the GitHub IDs of the HQ contributors from the text file list + /// + /// + public ActionResult GitHubGetContributorsResult() + { + var model = new GitHubContributorsModel(); + try + { + string configPath = Server.MapPath("~/config/githubhq.txt"); + if (!System.IO.File.Exists(configPath)) + { + LogHelper.Debug("Config file was not found: " + configPath); + return PartialView("~/Views/Partials/Home/GitHubContributors.cshtml", model); + } + + string[] login = System.IO.File.ReadAllLines(configPath).Where(x => x.Trim() != "").Distinct().ToArray(); + var contributors = ApplicationContext.ApplicationCache.RuntimeCache.GetCacheItem>("UmbracoGitHubContributors", + () => + { + var githubController = new GitHubController(); + var gitHubContributors = new List(); + foreach (var repo in Repositories) + { + var response = githubController.GetAllRepoContributors(repo); + if (response.StatusCode == HttpStatusCode.OK && + response.ResponseStatus == ResponseStatus.Completed) + { + gitHubContributors.AddRange(response.Data); + } + else + { + LogHelper.Warn(string.Format("Invalid HTTP response for repository {0}", repo)); + } + } + + var filteredContributors = gitHubContributors + .Where(g => !login.Contains(g.Author.Login)) + .GroupBy(g => g.Author.Id) + .OrderByDescending(c => c.Sum(g => g.Total)); + + List temp = new List(); + + foreach (var group in filteredContributors) + { + temp.Add(new GitHubGlobalContributorModel(group)); + } + + return temp; + + }, TimeSpan.FromDays(1)); + + + model.Contributors = contributors; + } + catch (Exception ex) + { + LogHelper.Error("Could not get GitHub Contributors", ex); + } + + return PartialView("~/Views/Partials/Home/GitHubContributors.cshtml", model); + } + } +} diff --git a/OurUmbraco/Community/Controllers/GitHubGlobalContributorModel.cs b/OurUmbraco/Community/Controllers/GitHubGlobalContributorModel.cs new file mode 100644 index 00000000..a5766ed3 --- /dev/null +++ b/OurUmbraco/Community/Controllers/GitHubGlobalContributorModel.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using OurUmbraco.Community.Models; + +namespace OurUmbraco.Community.Controllers +{ + public class GitHubGlobalContributorModel + { + public List Items { get; set; } + + public int Id + { + get { return Author.Id; } + } + + public int TotalCommits + { + get { return Items.Sum(x => x.Total); } + } + + public int TotalAdditions + { + get { return Items.Sum(x => x.TotalAdditions); } + } + + public int TotalDeletions + { + get { return Items.Sum(x => x.TotalDeletions); } + } + + public Author Author + { + get { return Items.First().Author; } + } + + public GitHubGlobalContributorModel(IEnumerable items) + { + Items = items.ToList(); + } + + } +} \ No newline at end of file diff --git a/OurUmbraco/Community/Models/GitHubContributorsModel.cs b/OurUmbraco/Community/Models/GitHubContributorsModel.cs new file mode 100644 index 00000000..592a5524 --- /dev/null +++ b/OurUmbraco/Community/Models/GitHubContributorsModel.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using OurUmbraco.Community.Controllers; + +namespace OurUmbraco.Community.Models +{ + public class GitHubContributorsModel : IGitHubContributorsModel + { + public List Contributors { get; set; } + } +} \ No newline at end of file diff --git a/OurUmbraco/Community/Models/GithubContributorModel.cs b/OurUmbraco/Community/Models/GithubContributorModel.cs new file mode 100644 index 00000000..3180b06e --- /dev/null +++ b/OurUmbraco/Community/Models/GithubContributorModel.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace OurUmbraco.Community.Models +{ + [DataContract] + public class GitHubContributorModel : IGitHubContributorModel + { + + private List _weeks; + + public int Total { get; set; } + public int TotalAdditions { get; set; } + public int TotalDeletions { get; set; } + + [DataMember(Name = "weeks")] + public List Weeks + { + get { return _weeks; } + + set + { + _weeks = value; + + + int totalAdditions = 0; + int totalDeletions = 0; + foreach (var week in _weeks) + { + totalDeletions += week.D; + totalAdditions += week.A; + } + + TotalAdditions = totalAdditions; + TotalDeletions = totalDeletions; + + } + + } + public Author Author { get; set; } + } + + [DataContract] + public class Week + { + /// + /// Timestamp + /// + public int W { get; set; } + + /// + /// Additions + /// + public int A { get; set; } + + /// + /// Deletions + /// + public int D { get; set; } + + /// + /// Commits + /// + public int C { get; set; } + } + + [DataContract] + public class Author + { + public string Login { get; set; } + + public int Id { get; set; } + + [DataMember(Name = "avatar_url")] + public string AvatarUrl { get; set; } + + [DataMember(Name = "gravatar_id")] + public string GravatarId { get; set; } + + public string Url { get; set; } + + [DataMember(Name = "html_url")] + public string HtmlUrl { get; set; } + + [DataMember(Name = "followers_url")] + public string FollowersUrl { get; set; } + + [DataMember(Name = "following_url")] + public string FollowingsUrl { get; set; } + + [DataMember(Name = "gists_url")] + public string GistsUrl { get; set; } + + [DataMember(Name = "starred_url")] + public string StarredUrl { get; set; } + + [DataMember(Name = "subscriptions_url")] + public string SubscriptionsUrl { get; set; } + + [DataMember(Name = "organizations_url")] + public string OrganizationsUrl { get; set; } + + [DataMember(Name = "repos_url")] + public string ReposUrl { get; set; } + + [DataMember(Name = "events_url")] + public string EventsUrl { get; set; } + + [DataMember(Name = "received_events_url")] + public string ReceivedEventsUrl { get; set; } + + public string Type { get; set; } + + [DataMember(Name = "site_admin")] + public bool SiteAdmin { get; set; } + } +} \ No newline at end of file diff --git a/OurUmbraco/Community/Models/IGitHubContributorModel.cs b/OurUmbraco/Community/Models/IGitHubContributorModel.cs new file mode 100644 index 00000000..39bddcb4 --- /dev/null +++ b/OurUmbraco/Community/Models/IGitHubContributorModel.cs @@ -0,0 +1,6 @@ +namespace OurUmbraco.Community.Models +{ + public interface IGitHubContributorModel + { + } +} \ No newline at end of file diff --git a/OurUmbraco/Community/Models/IGitHubContributorsModel.cs b/OurUmbraco/Community/Models/IGitHubContributorsModel.cs new file mode 100644 index 00000000..02f98006 --- /dev/null +++ b/OurUmbraco/Community/Models/IGitHubContributorsModel.cs @@ -0,0 +1,6 @@ +namespace OurUmbraco.Community.Models +{ + public interface IGitHubContributorsModel + { + } +} \ No newline at end of file diff --git a/OurUmbraco/Our/Api/GithubController.cs b/OurUmbraco/Our/Api/GithubController.cs new file mode 100644 index 00000000..471a27c1 --- /dev/null +++ b/OurUmbraco/Our/Api/GithubController.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using OurUmbraco.Community.Models; +using RestSharp; + +namespace OurUmbraco.Our.Api +{ + public class GitHubController + { + private const string RepositoryOwner = "Umbraco"; + private const string GitHubApiClient = "https://api.github.com"; + private const string UserAgent = "OurUmbraco"; + + /// + /// Get all contributors from GitHub Umbraco repositories + /// + /// + /// + public IRestResponse> GetAllRepoContributors(string repo) + { + var client = new RestClient(GitHubApiClient); + var request = new RestRequest(string.Format("/repos/{0}/{1}/stats/contributors", RepositoryOwner, repo), Method.GET); + client.UserAgent = UserAgent; + var response = client.Execute>(request); + return response; + } + } +} diff --git a/OurUmbraco/OurUmbraco.csproj b/OurUmbraco/OurUmbraco.csproj index 7e15d8a3..4d155f07 100644 --- a/OurUmbraco/OurUmbraco.csproj +++ b/OurUmbraco/OurUmbraco.csproj @@ -403,10 +403,16 @@ + + + + + + @@ -519,6 +525,7 @@ +