21 Commits

Author SHA1 Message Date
Niels Hartvig 2ffa61564c Removes RTs from twitter feed 2017-06-23 11:03:06 +02:00
Niels Hartvig a51e57737b Merge branch 'bjarnef-dev-githubfilecache' 2017-06-23 10:48:42 +02:00
Niels Hartvig 1c8fcef754 Removing throwing error upon github api not returning 200
(we already log it and the Accepted status with empty data set is
(hopefully) a throttle issue)
2017-06-23 10:47:50 +02:00
Niels Hartvig ab4ca0015e Merge with master 2017-06-23 10:38:52 +02:00
Niels Hartvig f9014d649b Fixes issue with contrib not counting commits but additions+deletions 2017-06-23 10:09:04 +02:00
hartvig adf67fd89f Merge pull request #116 from Weegy/GitGubContributors
Git gub contributors
2017-06-23 08:54:03 +02:00
Sebastiaan Janssen 805709a91b Set frontend auth cookie to be sent over secure channel only 2017-06-23 08:22:44 +02:00
Marcel Wege 40a6aaa1a8 foreach to Linq.SUM 2017-06-22 17:29:57 +02:00
Marcel Wege 5e51dad445 GitHubContributors 2017-06-22 16:56:35 +02:00
hartvig f2d9cc0c0b Merge pull request #110 from emmagarland/githubcontributors
Github contributors
2017-06-22 14:43:45 +02:00
Niels Hartvig a40f8f9a28 Fixes cache setting 2017-06-22 14:39:38 +02:00
Niels Hartvig 355c4184ef Update github list to only include people from the last 12 months 2017-06-22 14:37:32 +02:00
Sebastiaan Janssen 50bdd7647a Redirect docs for products to Add-on 2017-06-22 10:16:14 +02:00
hartvig 47c85e633b Adding umbLeeds to meetup list 2017-06-19 15:31:34 +02:00
Sebastiaan Janssen f82cda981c Whitelist branches to get the documentation from and make it a little bit safer (don't start with deleting everything until we're pretty sure it's going to be rebuilt correctly afterwards). 2017-06-13 13:35:55 +02:00
Sebastiaan Janssen aded9ec437 Fixing docs 2017-06-13 10:17:45 +02:00
Shannon bdcad7665f fixes string interpolation 2017-06-07 08:34:12 +02:00
Per Ploug 850b61fc4f Merge pull request #114 from umbraco/lesson-documentation-controller
remove dollars
2017-06-07 08:04:30 +02:00
Bjarne Fyrstenborg 1ea05d8107 Write contributors to json file and use file cached disk value 2017-06-06 16:27:21 +02:00
Emma Garland 60b7347586 Updated contributions to commits 2017-06-05 20:55:12 +01:00
Emma Garland b35d45bf31 Updated contribution link 2017-06-05 20:38:26 +01:00
11 changed files with 237 additions and 68 deletions
@@ -140,7 +140,6 @@ else
<div class="col-md-12">
<h1 class="text-center">GitHub Contributions</h1>
<p>
Contributions to Umbraco GitHub repositories
<small class="link-list">
Contributions to default branches for
<a href="https://github.com/umbraco/Umbraco-CMS" target="_blank" title="Umbraco-CMS">Umbraco-CMS</a>,
@@ -153,7 +152,7 @@ else
</p>
</div>
<div class="col-md-12">
<small>GitHub contributors</small>
<small>Contributions the last 12 months (number indicating accepted commits)</small>
</div>
<div class="col-md-12 flex forum-thread" id="github-contributors">
@@ -161,7 +160,7 @@ else
</div>
<div class="col-md-12 goto-forum">
<a class="button green" href="https://github.com/umbraco" target="_blank">Contribute to Umbraco on GitHub &rarr;</a>
<a class="button green" href="https://our.umbraco.org/contribute">Contribute to Umbraco &rarr;</a>
</div>
</div>
@@ -4,12 +4,10 @@
{
foreach (var contributor in Model.Contributors)
{
var author = contributor.Author;
if (author != null)
{
<a href="@author.HtmlUrl" class="contributor" target="_blank" title="@author.Login">
<a href="@contributor.AuthorUrl" class="contributor" target="_blank" title="@contributor.AuthorLogin">
<div class="avatar">
<img alt="@author.Login" src="@author.AvatarUrl&s=112" />
<img alt="@contributor.AuthorLogin" src="@contributor.AuthorAvatarUrl&s=112"
srcset="@contributor.AuthorAvatarUrl&s=224 2x, @contributor.AuthorAvatarUrl&s=336 3x" />
<span class="contrib-count" title="@(contributor.TotalCommits + " " + (contributor.TotalCommits == 1 ? "contribution" : "contributions"))">
@contributor.TotalCommits
</span>
@@ -22,7 +20,6 @@
</div>
</div>
</a>
}
}
<div class="loadmore">
<a class="button transparent" href="#" onclick="loadAllGitHubContributors(); return false;">Load more</a>
@@ -32,4 +32,5 @@ UmbCardiff
meet-umbraco-the-umbraco-meetup
umBristol
Oslo-Umbraco-Meetup
Orange-County-Umbraco-Web-Technologies
Orange-County-Umbraco-Web-Technologies
umbLeeds
+2
View File
@@ -2,5 +2,7 @@
<configuration>
<sources>
<add url="https://github.com/umbraco/UmbracoDocs/archive/master.zip" folder="" />
<!-- Comma separated list of branches we will pull documentation from -->
<allowedBranches>master</allowedBranches>
</sources>
</configuration>
+6 -2
View File
@@ -308,7 +308,7 @@
</buildProviders>
</compilation>
<authentication mode="Forms">
<forms name="yourAuthCookie" loginUrl="login.aspx" protection="All" path="/" slidingExpiration="true" timeout="525600" />
<forms requireSSL="true" name="yourAuthCookie" loginUrl="login.aspx" protection="All" path="/" slidingExpiration="true" timeout="525600" />
</authentication>
<authorization>
<allow users="?" />
@@ -506,6 +506,10 @@
</conditions>
<action type="Redirect" url="{C:1}" redirectType="Permanent" appendQueryString="true" />
</rule>
<rule name="DocsProductsToAddons" patternSyntax="Wildcard" stopProcessing="true">
<match url="documentation/products/*" />
<action type="Redirect" url="/documentation/Add-ons/{R:1}" redirectType="Permanent" appendQueryString="true" />
</rule>
<!-- WIKI -->
<rule name="RedirectWikiEvents" patternSyntax="Wildcard">
<match url="wiki/reference/api-cheatsheet/using-applicationbase-to-register-events/*" />
@@ -691,4 +695,4 @@
</httpProtocol>
</system.webServer>
</location>
</configuration>
</configuration>
@@ -9,6 +9,9 @@ using RestSharp;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Web.Mvc;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.Text;
namespace OurUmbraco.Community.Controllers
{
@@ -27,68 +30,140 @@ namespace OurUmbraco.Community.Controllers
"Umbraco.Deploy.ValueConnectors"
};
protected string JsonPath
{
get { return Server.MapPath("~/App_Data/TEMP/GithubContributors.json"); }
}
public List<GitHubCachedGlobalContributorModel> GetCachedContributors()
{
if (!System.IO.File.Exists(JsonPath)) throw new Exception("The JSON file doesn't exist on disk");
List<GitHubCachedGlobalContributorModel> temp = new List<GitHubCachedGlobalContributorModel>();
// Parse the JSON file from disk
foreach (JToken token in Skybrud.Essentials.Json.JsonUtils.LoadJsonArray(JsonPath))
{
temp.Add(token.ToObject<GitHubCachedGlobalContributorModel>());
}
return temp;
}
/// <summary>
/// Gets data for all GitHub contributors for all listed Umbraco repositories,
/// excluding the GitHub IDs of the HQ contributors from the text file list
/// </summary>
/// <returns></returns>
public ActionResult GitHubGetContributorsResult()
public ActionResult GitHubGetContributorsResult(bool force = false)
{
var model = new GitHubContributorsModel();
model.Contributors = new List<GitHubCachedGlobalContributorModel>();
try
{
string configPath = Server.MapPath("~/config/githubhq.txt");
if (!System.IO.File.Exists(configPath))
// If the file exists on disk and hasn't expired (AKA not older than one day), we should just load that
if (force == false && System.IO.File.Exists(JsonPath) && System.IO.File.GetLastWriteTimeUtc(JsonPath) >= DateTime.UtcNow.AddDays(-1))
{
LogHelper.Debug<GitHubContributorController>("Config file was not found: " + configPath);
model.Contributors = GetCachedContributors();
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<List<GitHubGlobalContributorModel>>("UmbracoGitHubContributors",
() =>
{
var githubController = new GitHubController();
var gitHubContributors = new List<GitHubContributorModel>();
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<IGitHubContributorsModel>(string.Format("Invalid HTTP response for repository {0}", repo));
}
}
try
{
// Load the contributors via the GitHub API and map to the cached model
var contributors = GetContributors().Select(x => new GitHubCachedGlobalContributorModel(x));
var filteredContributors = gitHubContributors
.Where(g => !login.Contains(g.Author.Login))
.GroupBy(g => g.Author.Id)
.OrderByDescending(c => c.Sum(g => g.Total));
// Serialize the contributors to raw JSON
string rawJson = JsonConvert.SerializeObject(contributors, Formatting.Indented);
List<GitHubGlobalContributorModel> temp = new List<GitHubGlobalContributorModel>();
// Save the JSON to disk
System.IO.File.WriteAllText(JsonPath, rawJson, Encoding.UTF8);
foreach (var group in filteredContributors)
{
temp.Add(new GitHubGlobalContributorModel(group));
}
model.Contributors = contributors.ToList();
}
catch (Exception ex)
{
// Log the error so we can debug it later
LogHelper.Error<GitHubContributorController>("Unable to load GitHub contributors from the GitHub API", ex);
return temp;
// Load the contributors from the cache (if we have any)
model.Contributors = GetCachedContributors();
}
}, TimeSpan.FromDays(1));
model.Contributors = contributors;
}
catch (Exception ex)
{
LogHelper.Error<IGitHubContributorsModel>("Could not get GitHub Contributors", ex);
// Log the error so we can debug it later
LogHelper.Error<GitHubContributorController>("Unable to load GitHub contributors from the GitHub API", ex);
}
return PartialView("~/Views/Partials/Home/GitHubContributors.cshtml", model);
}
public List<GitHubGlobalContributorModel> GetContributors()
{
string configPath = Server.MapPath("~/config/githubhq.txt");
if (!System.IO.File.Exists(configPath))
{
LogHelper.Debug<GitHubContributorController>("Config file was not found: " + configPath);
throw new Exception("Config file was not found: " + configPath);
}
string[] login = System.IO.File.ReadAllLines(configPath).Where(x => x.Trim() != "").Distinct().ToArray();
var githubController = new GitHubController();
var gitHubContributors = new List<GitHubContributorModel>();
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<IGitHubContributorsModel>(string.Format("Invalid HTTP response for repository {0}", repo));
}
}
// filter to only include items from the last year (if we ran 4.6.1 we could have used ToUnixTimeSeconds())
var filteredRange = DateTime.UtcNow.AddYears(-1).Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
foreach (var contrib in gitHubContributors)
{
var contribWeeks = contrib.Weeks.Where(x => x.W >= filteredRange).ToList();
contrib.TotalAdditions = contribWeeks.Sum(x => x.A);
contrib.TotalDeletions = contribWeeks.Sum(x => x.D);
contrib.Total = contribWeeks.Sum(x => x.C);
}
var filteredContributors = gitHubContributors
.Where(g => !login.Contains(g.Author.Login))
.GroupBy(g => g.Author.Id)
.OrderByDescending(c => c.Sum(g => g.Total));
List<GitHubGlobalContributorModel> temp = new List<GitHubGlobalContributorModel>();
foreach (var group in filteredContributors)
{
temp.Add(new GitHubGlobalContributorModel(group));
}
return temp;
}
}
}
@@ -4,6 +4,39 @@ using OurUmbraco.Community.Models;
namespace OurUmbraco.Community.Controllers
{
public class GitHubCachedGlobalContributorModel
{
public int AuthorId { get; set; }
public string AuthorLogin { get; set; }
public string AuthorUrl { get; set; }
public string AuthorAvatarUrl { get; set; }
public int TotalCommits { get; set; }
public int TotalAdditions { get; set; }
public int TotalDeletions { get; set; }
public GitHubCachedGlobalContributorModel() { }
public GitHubCachedGlobalContributorModel(GitHubGlobalContributorModel contributor)
{
AuthorId = contributor.Author.Id;
AuthorLogin = contributor.Author.Login;
AuthorUrl = contributor.Author.HtmlUrl;
AuthorAvatarUrl = contributor.Author.AvatarUrl;
TotalCommits = contributor.TotalCommits;
TotalAdditions = contributor.TotalAdditions;
TotalDeletions = contributor.TotalDeletions;
}
}
public class GitHubGlobalContributorModel
{
public List<GitHubContributorModel> Items { get; set; }
@@ -60,7 +60,8 @@ namespace OurUmbraco.Community.Controllers
x.CreatedBy.UserIdentifier.ScreenName.ToLowerInvariant().ContainsAny(usernameFilter) ==
false
&& x.UserMentions.Any(m => m.ScreenName.ContainsAny(usernameFilter)) == false
&& x.Text.ToLowerInvariant().ContainsAny(wordFilter) == false)
&& x.Text.ToLowerInvariant().ContainsAny(wordFilter) == false
&& x.Text.StartsWith("RT ") == false)
.Take(numberOfResults)
.ToArray();
}
@@ -5,6 +5,6 @@ namespace OurUmbraco.Community.Models
{
public class GitHubContributorsModel : IGitHubContributorsModel
{
public List<GitHubGlobalContributorModel> Contributors { get; set; }
public List<GitHubCachedGlobalContributorModel> Contributors { get; set; }
}
}
@@ -3,13 +3,10 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Web;
using System.Web.Hosting;
using System.Xml;
using Examine;
using ICSharpCode.SharpZipLib.Zip;
using Newtonsoft.Json;
using Umbraco.Core.Logging;
using ZipFile = System.IO.Compression.ZipFile;
@@ -122,10 +119,36 @@ namespace OurUmbraco.Documentation.Busineslogic.GithubSourcePull
public void Process(string url, string foldername)
{
var zip = Download(url, foldername);
RemoveExistingDocumentation(RootFolder);
var unzippedPath = string.Empty;
var branchAllowed = false;
using (var zipFile = ZipFile.OpenRead(zip))
{
var directory = zipFile.Entries.FirstOrDefault();
if (directory != null)
{
var branch = directory.FullName.Replace("/", string.Empty).Replace("UmbracoDocs-", string.Empty);
branchAllowed = IsBranchWhiteListed(branch);
if (branchAllowed)
{
unzippedPath = RootFolder + Path.DirectorySeparatorChar +
directory.FullName.Replace('/', Path.DirectorySeparatorChar);
if (Directory.Exists(unzippedPath))
//delete the directory we're trying to unzip to
RemoveUnzippedFiles(unzippedPath);
}
}
}
if (branchAllowed == false)
return;
ZipFile.ExtractToDirectory(zip, RootFolder);
var unzippedPath = RootFolder + "\\UmbracoDocs-StarterkitLessons\\";
RemoveUnzippedFiles(RootFolder, ignoreDirectories: new List<string> { unzippedPath });
foreach (var directory in new DirectoryInfo(unzippedPath).GetDirectories())
Directory.Move(directory.FullName, RootFolder + "\\" + directory.Name);
foreach (var file in new DirectoryInfo(unzippedPath).GetFiles())
@@ -139,6 +162,25 @@ namespace OurUmbraco.Documentation.Busineslogic.GithubSourcePull
ExamineManager.Instance.IndexProviderCollection["documentationIndexer"].RebuildIndex();
}
private bool IsBranchWhiteListed(string branch)
{
var xmlNodeList = Configuration.SelectNodes("//allowedBranches");
if (xmlNodeList == null || xmlNodeList.Count <= 0)
return false;
var node = xmlNodeList[0];
if (string.IsNullOrWhiteSpace(node.InnerText))
return false;
var allowedBranches = node.InnerText.Split(',');
var isBranchWhitelisted = allowedBranches.Any(allowedBranch => allowedBranch.Equals(branch, StringComparison.InvariantCultureIgnoreCase));
if (isBranchWhitelisted == false)
LogHelper.Warn<ZipDownloader>(string.Format("The branch {0} is not allowed in githubpull.config, will not process documentation any further", branch));
return isBranchWhitelisted;
}
public void BuildSitemap(string foldername)
{
var folder = new DirectoryInfo(Path.Combine(RootFolder, foldername));
@@ -158,7 +200,7 @@ namespace OurUmbraco.Documentation.Busineslogic.GithubSourcePull
public List<SiteMapItem> directories { get; set; }
//public string url => $"http://localhost:24292/documentation{this.path}/?altTemplate=Lesson";
public string url
{
get
@@ -181,7 +223,7 @@ namespace OurUmbraco.Documentation.Busineslogic.GithubSourcePull
directories = list,
hasChildren = dir.GetDirectories().Any()
};
foreach (var child in dir.GetDirectories().Where(x => x.Name != "images"))
{
list.Add(GetFolderStructure(child, rootPath, level + 1));
@@ -452,21 +494,35 @@ namespace OurUmbraco.Documentation.Busineslogic.GithubSourcePull
return path;
}
private static void RemoveExistingDocumentation(string folder)
private static void RemoveUnzippedFiles(string folder, List<string> ignoreDirectories = null)
{
if (Directory.Exists(folder))
{
foreach (var directory in Directory.GetDirectories(folder))
Retry.Do(() => Directory.Delete(directory, true), TimeSpan.FromSeconds(1), 5);
{
if (ignoreDirectories == null || ignoreDirectories.Any() == false)
{
//there are no ignores, so proceed to delete it
Retry.Do(() => Directory.Delete(directory, true), TimeSpan.FromSeconds(1), 5);
}
else
{
foreach (var ignoreDirectory in ignoreDirectories)
{
if (ignoreDirectory.TrimEnd('\\').Equals(directory.TrimEnd('\\'),
StringComparison.InvariantCultureIgnoreCase) == false)
{
//path is NOT in the ignore list, so proceed to delete it
Retry.Do(() => Directory.Delete(directory, true), TimeSpan.FromSeconds(1), 5);
}
}
}
}
foreach (var mdfile in Directory.GetFiles(folder, "*.md"))
Retry.Do(() => File.Delete(mdfile), TimeSpan.FromSeconds(1), 5);
}
else
{
Directory.CreateDirectory(folder);
}
}
private readonly Events _events = new Events();
@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using System.Text.RegularExpressions;
using MarkdownDeep;
@@ -85,7 +86,7 @@ namespace OurUmbraco.Documentation.Busineslogic
//Need to ensure we dont append the image links as they 404 if we add altTemplate
if (AppendAltLessonLink && rawUrl.StartsWith("images/") == false)
{
return mdUrlTag.Replace(rawUrl, $"{rawUrl.EnsureNoDotsInUrl()}?altTemplate=Lesson");
return mdUrlTag.Replace(rawUrl, string.Format("{0}?altTemplate=Lesson", rawUrl.EnsureNoDotsInUrl()));
}
return mdUrlTag.Replace(rawUrl, rawUrl.EnsureNoDotsInUrl());