Add changelog to Github Actions releases

Comments

Using Github Actions you can create new releases in Github easily. This is done by using Create a Release Action.
You can add Markdown to the body of this Action. One common detail that is usually included with releases is the list of the changes from last release. After doing some research and many pushes to Github I found the solution and I thought it would be a good idea to put it somewhere!

name: Release-Me

on:
  push:
    tags:
      - '*'

jobs:
  release:
    runs-on: windows-latest
    steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: '0'
    - name: Get the version
      id: get_version
      run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
      shell: bash
    - name: Get changes
      id: get_changes
      run: echo ::set-output name=changes::$(git log --oneline $(git describe --tags --abbrev=0 @^)..@ | sed 's/$/%0A/')
      shell: bash
    - name: Create release
      id: create_release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ steps.get_version.outputs.VERSION }}
        release_name: ${{ steps.get_version.outputs.VERSION }}
        body: |
          ## Changelog
          ${{ steps.get_changes.outputs.changes }}
        draft: true
        prerelease: true

This will list all the changes from current to last git tag. It is very important to set fetch-depth to 0 so the git history is loaded for the branch.

One thing that took me some time to figure out was that following characters have to be escaped and the runner will unescape them in reverse.

  • % to %25
  • \n to %0A
  • \r to %0D

Extract Front Matter from Markdown file in .NET Core using Markdig

Comments

Adding YAML front matter to Markdown is a great way to add metadata to your content. Front matter is the first thing in the file and must be added in a special bock. Something similar to following block:

---
title: This can be title of your document
createDate: 2020-06-16
---

To extract this information from your file you can use Markdig. After extraction you can use a library like YamlDotNet to deserialize this content into an object.

This is sample C# code that achieves what discussed above:

var pipeline = new MarkdownPipelineBuilder()
    .UseYamlFrontMatter()
    .Build();

var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
pipeline.Setup(renderer);

var document = Markdown.Parse(markdown, pipeline);

// extract the front matter from markdown document
var yamlBlock = document.Descendants<YamlFrontMatterBlock>().FirstOrDefault();

var yaml = yamlBlock.Lines.ToString();

// deserialize the yaml block into a custom type
var deserializer = new DeserializerBuilder()
    .WithNamingConvention(CamelCaseNamingConvention.Instance)
    .Build();

var metadata = deserializer.Deserialize<Metadata>(yaml);

// finally we can render the markdown content as html if necessary
renderer.Render(document);
await writer.FlushAsync();
var html = writer.ToString();

User settings for Visual Studio Code

Comments

These are some of the User Settings I change in Visual Studio Code:

{
    // firewalls in some organizations causes the extension download to fail
    "http.proxyStrictSSL": false,

    // a good way to see those useless white spaces
    "editor.renderWhitespace": "boundary",

    // not a fan of minimap. code files shouldn't be too long to need a minimap
    "editor.minimap.enabled": false,

    // why not?!
    "editor.codeLens": true,
    "typescript.referencesCodeLens.enabled": true,
    "typescript.implementationsCodeLens.enabled": true,

    // with the default title it's hard to distinguish between multiple open insutances
    "window.title": "${rootName}${separator}${activeEditorShort} ${dirty}",

    // can cause frustration with Angular projects that auto reload when a file is saved
    "files.autoSave": "off",
}


Group by day, week, month, quarter and year in Entity Framework (Linq to SQL)

Comments

Consider having following Product class and you want to get some stats based on the creation date of the product.

public class Product
{
    public int Id { get; set; }
    public string { get; set; }
    public DateTime CreateDate { get; set; }
}

What we want to query here is the number of products created every day, week, month, querter and year; we also want to get the stats for certain number of days.

public ActionResult Stats(int days = 365, DateGroupType group = DateGroupType.Week)
{
    var startDate = DateTime.Now.AddDays(-1 * days);
    var allStats = _db
        .Products
        .Where(x => x.CreateDate > startDate);

    var groupedStats =  GroupByDate(allStats, group, startDate);

    var stats = groupedStats.Select(x => new
        {
            x.Key,
            Count = x.Count()
        })
        .ToList()
        .Select(x => new Stat
        {
            Created = GetDate(group, x.Key.Value, startDate),
            x.Count
        })
        .OrderBy(x => x.Created)
        .ToList();

    return stats;
}

private IQueryable<IGrouping<int?, Product>> GroupByDate(IQueryable<Product> products, DateGroupType group, DateTime startDate)
{
    switch (group)
    {
        case DateGroupType.Day:
            return products.GroupBy(x => SqlFunctions.DateDiff("dd", startDate, x.CreateDate));

        case DateGroupType.Week:
            return products.GroupBy(x => SqlFunctions.DateDiff("ww", startDate, x.CreateDate));

        case DateGroupType.Month:
            return products.GroupBy(x => SqlFunctions.DateDiff("mm", startDate, x.CreateDate));

        case DateGroupType.Quarter:
            return products.GroupBy(x => SqlFunctions.DateDiff("qq", startDate, x.CreateDate));

        case DateGroupType.Year:
            return products.GroupBy(x => SqlFunctions.DateDiff("yy", startDate, x.CreateDate));

        default:
            throw new NotSupportedException($"Grouping by '{group}' is not supported");
    }
}

private DateTime GetDate(DateGroupType group, int diff, DateTime startDate)
{
    switch (group)
    {
        case DateGroupType.Day:
            return startDate.AddDays(diff);

        case DateGroupType.Week:
            return startDate.AddDays(diff * 7);

        case DateGroupType.Month:
            return startDate.AddMonths(diff);

        case DateGroupType.Quarter:
            return startDate.AddMonths(diff * 3);

        case DateGroupType.Year:
            return startDate.AddYears(diff);

        default:
            throw new NotSupportedException($"Grouping by '{group}' is not supported");
    }
}

There are a few classes and enumerations used in this code that are listed below:

public class Stat
{
    public DateTime CreateDate { get; set; }
    public int Count { get; set; }
}

public enum DateGroupType
{
    Day,
    Week,
    Month,
    Quarter,
    Year
}