Merge pull request #11314 from bdach/changelog-refactor

Refactor changelog code after migrating away from production web instance
This commit is contained in:
Dean Herbert
2020-12-28 11:39:48 +09:00
committed by GitHub
5 changed files with 344 additions and 188 deletions

View File

@ -1,8 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Humanizer;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Changelog; using osu.Game.Overlays.Changelog;
@ -12,13 +17,61 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture] [TestFixture]
public class TestSceneChangelogOverlay : OsuTestScene public class TestSceneChangelogOverlay : OsuTestScene
{ {
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private readonly Dictionary<string, APIUpdateStream> streams;
private readonly Dictionary<string, APIChangelogBuild> builds;
private APIChangelogBuild requestedBuild;
private TestChangelogOverlay changelog; private TestChangelogOverlay changelog;
protected override bool UseOnlineAPI => true; public TestSceneChangelogOverlay()
{
streams = APIUpdateStream.KNOWN_STREAMS.Keys.Select((stream, id) => new APIUpdateStream
{
Id = id + 1,
Name = stream,
DisplayName = stream.Humanize(), // not quite there, but good enough.
}).ToDictionary(stream => stream.Name);
string version = DateTimeOffset.Now.ToString("yyyy.Mdd.0");
builds = APIUpdateStream.KNOWN_STREAMS.Keys.Select(stream => new APIChangelogBuild
{
Version = version,
DisplayVersion = version,
UpdateStream = streams[stream],
ChangelogEntries = new List<APIChangelogEntry>()
}).ToDictionary(build => build.UpdateStream.Name);
foreach (var stream in streams.Values)
stream.LatestBuild = builds[stream.Name];
}
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{ {
requestedBuild = null;
dummyAPI.HandleRequest = request =>
{
switch (request)
{
case GetChangelogRequest changelogRequest:
var changelogResponse = new APIChangelogIndex
{
Streams = streams.Values.ToList(),
Builds = builds.Values.ToList()
};
changelogRequest.TriggerSuccess(changelogResponse);
break;
case GetChangelogBuildRequest buildRequest:
if (requestedBuild != null)
buildRequest.TriggerSuccess(requestedBuild);
break;
}
};
Child = changelog = new TestChangelogOverlay(); Child = changelog = new TestChangelogOverlay();
}); });
@ -41,26 +94,60 @@ namespace osu.Game.Tests.Visual.Online
} }
[Test] [Test]
[Ignore("needs to be updated to not be so server dependent")]
public void ShowWithBuild() public void ShowWithBuild()
{ {
AddStep(@"Show with Lazer 2018.712.0", () => showBuild(() => new APIChangelogBuild
{ {
changelog.ShowBuild(new APIChangelogBuild Version = "2018.712.0",
DisplayVersion = "2018.712.0",
UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME],
ChangelogEntries = new List<APIChangelogEntry>
{ {
Version = "2018.712.0", new APIChangelogEntry
DisplayVersion = "2018.712.0",
UpdateStream = new APIUpdateStream { Id = 5, Name = OsuGameBase.CLIENT_STREAM_NAME },
ChangelogEntries = new List<APIChangelogEntry>
{ {
new APIChangelogEntry Type = ChangelogEntryType.Fix,
Category = "osu!",
Title = "Fix thing",
MessageHtml = "Additional info goes here.",
Repository = "osu",
GithubPullRequestId = 11100,
GithubUser = new APIChangelogUser
{ {
Category = "Test", OsuUsername = "smoogipoo",
Title = "Title", UserId = 1040328
MessageHtml = "Message",
} }
},
new APIChangelogEntry
{
Type = ChangelogEntryType.Add,
Category = "osu!",
Title = "Add thing",
Major = true,
Repository = "ppy/osu-framework",
GithubPullRequestId = 4444,
GithubUser = new APIChangelogUser
{
DisplayName = "frenzibyte",
GithubUrl = "https://github.com/frenzibyte"
}
},
new APIChangelogEntry
{
Type = ChangelogEntryType.Misc,
Category = "Code quality",
Title = "Clean up thing",
GithubUser = new APIChangelogUser
{
DisplayName = "some dude"
}
},
new APIChangelogEntry
{
Type = ChangelogEntryType.Misc,
Category = "Code quality",
Title = "Clean up another thing"
} }
}); }
}); });
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0); AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
@ -71,35 +158,38 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestHTMLUnescaping() public void TestHTMLUnescaping()
{ {
AddStep(@"Ensure HTML string unescaping", () => showBuild(() => new APIChangelogBuild
{ {
changelog.ShowBuild(new APIChangelogBuild Version = "2019.920.0",
DisplayVersion = "2019.920.0",
UpdateStream = new APIUpdateStream
{ {
Version = "2019.920.0", Name = "Test",
DisplayVersion = "2019.920.0", DisplayName = "Test"
UpdateStream = new APIUpdateStream },
ChangelogEntries = new List<APIChangelogEntry>
{
new APIChangelogEntry
{ {
Name = "Test", Category = "Testing HTML strings unescaping",
DisplayName = "Test" Title = "Ensuring HTML strings are being unescaped",
}, MessageHtml = "&quot;&quot;&quot;This text should appear triple-quoted&quot;&quot;&quot; &gt;_&lt;",
ChangelogEntries = new List<APIChangelogEntry> GithubUser = new APIChangelogUser
{
new APIChangelogEntry
{ {
Category = "Testing HTML strings unescaping", DisplayName = "Dummy",
Title = "Ensuring HTML strings are being unescaped", OsuUsername = "Dummy",
MessageHtml = "&quot;&quot;&quot;This text should appear triple-quoted&quot;&quot;&quot; &gt;_&lt;", }
GithubUser = new APIChangelogUser },
{ }
DisplayName = "Dummy",
OsuUsername = "Dummy",
}
},
}
});
}); });
} }
private void showBuild(Func<APIChangelogBuild> build)
{
AddStep("set up build", () => requestedBuild = build.Invoke());
AddStep("show build", () => changelog.ShowBuild(requestedBuild));
}
private class TestChangelogOverlay : ChangelogOverlay private class TestChangelogOverlay : ChangelogOverlay
{ {
public new List<APIUpdateStream> Streams => base.Streams; public new List<APIUpdateStream> Streams => base.Streams;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osuTK.Graphics; using osuTK.Graphics;
@ -27,34 +28,16 @@ namespace osu.Game.Online.API.Requests.Responses
public bool Equals(APIUpdateStream other) => Id == other?.Id; public bool Equals(APIUpdateStream other) => Id == other?.Id;
public ColourInfo Colour internal static readonly Dictionary<string, Color4> KNOWN_STREAMS = new Dictionary<string, Color4>
{ {
get ["stable40"] = new Color4(102, 204, 255, 255),
{ ["stable"] = new Color4(34, 153, 187, 255),
switch (Name) ["beta40"] = new Color4(255, 221, 85, 255),
{ ["cuttingedge"] = new Color4(238, 170, 0, 255),
case "stable40": [OsuGameBase.CLIENT_STREAM_NAME] = new Color4(237, 18, 33, 255),
return new Color4(102, 204, 255, 255); ["web"] = new Color4(136, 102, 238, 255)
};
case "stable": public ColourInfo Colour => KNOWN_STREAMS.TryGetValue(Name, out var colour) ? colour : new Color4(0, 0, 0, 255);
return new Color4(34, 153, 187, 255);
case "beta40":
return new Color4(255, 221, 85, 255);
case "cuttingedge":
return new Color4(238, 170, 0, 255);
case OsuGameBase.CLIENT_STREAM_NAME:
return new Color4(237, 18, 33, 255);
case "web":
return new Color4(136, 102, 238, 255);
default:
return new Color4(0, 0, 0, 255);
}
}
}
} }
} }

View File

@ -9,14 +9,8 @@ using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using System; using System;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using System.Net;
using osuTK;
using osu.Framework.Extensions.Color4Extensions;
namespace osu.Game.Overlays.Changelog namespace osu.Game.Overlays.Changelog
{ {
@ -63,126 +57,7 @@ namespace osu.Game.Overlays.Changelog
Margin = new MarginPadding { Top = 35, Bottom = 15 }, Margin = new MarginPadding { Top = 35, Bottom = 15 },
}); });
var fontLarge = OsuFont.GetFont(size: 16); ChangelogEntries.AddRange(categoryEntries.Select(entry => new ChangelogEntry(entry)));
var fontMedium = OsuFont.GetFont(size: 12);
foreach (var entry in categoryEntries)
{
var entryColour = entry.Major ? colours.YellowLight : Color4.White;
LinkFlowContainer title;
var titleContainer = new Container
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Vertical = 5 },
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreRight,
Size = new Vector2(10),
Icon = entry.Type == ChangelogEntryType.Fix ? FontAwesome.Solid.Check : FontAwesome.Solid.Plus,
Colour = entryColour.Opacity(0.5f),
Margin = new MarginPadding { Right = 5 },
},
title = new LinkFlowContainer
{
Direction = FillDirection.Full,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.BottomLeft,
}
}
};
title.AddText(entry.Title, t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
if (!string.IsNullOrEmpty(entry.Repository))
{
title.AddText(" (", t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl,
creationParameters: t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
title.AddText(")", t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
}
title.AddText("by ", t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
t.Padding = new MarginPadding { Left = 10 };
});
if (entry.GithubUser != null)
{
if (entry.GithubUser.UserId != null)
{
title.AddUserLink(new User
{
Username = entry.GithubUser.OsuUsername,
Id = entry.GithubUser.UserId.Value
}, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
});
}
else if (entry.GithubUser.GithubUrl != null)
{
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
});
}
else
{
title.AddText(entry.GithubUser.DisplayName, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
});
}
}
ChangelogEntries.Add(titleContainer);
if (!string.IsNullOrEmpty(entry.MessageHtml))
{
var message = new TextFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
};
// todo: use markdown parsing once API returns markdown
message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t =>
{
t.Font = fontMedium;
t.Colour = colourProvider.Foreground1;
});
ChangelogEntries.Add(message);
}
}
} }
} }

View File

@ -0,0 +1,202 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Net;
using System.Text.RegularExpressions;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Changelog
{
public class ChangelogEntry : FillFlowContainer
{
private readonly APIChangelogEntry entry;
[Resolved]
private OsuColour colours { get; set; }
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
private FontUsage fontLarge;
private FontUsage fontMedium;
public ChangelogEntry(APIChangelogEntry entry)
{
this.entry = entry;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Vertical;
}
[BackgroundDependencyLoader]
private void load()
{
fontLarge = OsuFont.GetFont(size: 16);
fontMedium = OsuFont.GetFont(size: 12);
Children = new[]
{
createTitle(),
createMessage()
};
}
private Drawable createTitle()
{
var entryColour = entry.Major ? colours.YellowLight : Color4.White;
LinkFlowContainer title;
var titleContainer = new Container
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding { Vertical = 5 },
Children = new Drawable[]
{
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreRight,
Size = new Vector2(10),
Icon = getIconForChangelogEntry(entry.Type),
Colour = entryColour.Opacity(0.5f),
Margin = new MarginPadding { Right = 5 },
},
title = new LinkFlowContainer
{
Direction = FillDirection.Full,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.BottomLeft,
}
}
};
title.AddText(entry.Title, t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
if (!string.IsNullOrEmpty(entry.Repository))
addRepositoryReference(title, entryColour);
if (entry.GithubUser != null)
addGithubAuthorReference(title, entryColour);
return titleContainer;
}
private void addRepositoryReference(LinkFlowContainer title, Color4 entryColour)
{
title.AddText(" (", t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl,
t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
title.AddText(")", t =>
{
t.Font = fontLarge;
t.Colour = entryColour;
});
}
private void addGithubAuthorReference(LinkFlowContainer title, Color4 entryColour)
{
title.AddText("by ", t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
t.Padding = new MarginPadding { Left = 10 };
});
if (entry.GithubUser.UserId != null)
{
title.AddUserLink(new User
{
Username = entry.GithubUser.OsuUsername,
Id = entry.GithubUser.UserId.Value
}, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
});
}
else if (entry.GithubUser.GithubUrl != null)
{
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
});
}
else
{
title.AddText(entry.GithubUser.DisplayName, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;
});
}
}
private Drawable createMessage()
{
if (string.IsNullOrEmpty(entry.MessageHtml))
return Empty();
var message = new TextFlowContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
};
// todo: use markdown parsing once API returns markdown
message.AddText(WebUtility.HtmlDecode(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty)), t =>
{
t.Font = fontMedium;
t.Colour = colourProvider.Foreground1;
});
return message;
}
private static IconUsage getIconForChangelogEntry(ChangelogEntryType entryType)
{
// compare: https://github.com/ppy/osu-web/blob/master/resources/assets/coffee/react/_components/changelog-entry.coffee#L8-L11
switch (entryType)
{
case ChangelogEntryType.Add:
return FontAwesome.Solid.Plus;
case ChangelogEntryType.Fix:
return FontAwesome.Solid.Check;
case ChangelogEntryType.Misc:
return FontAwesome.Regular.Circle;
default:
throw new ArgumentOutOfRangeException(nameof(entryType), $"Unrecognised entry type {entryType}");
}
}
}
}

View File

@ -8,5 +8,11 @@ namespace osu.Game.Overlays.Changelog
public class ChangelogUpdateStreamControl : OverlayStreamControl<APIUpdateStream> public class ChangelogUpdateStreamControl : OverlayStreamControl<APIUpdateStream>
{ {
protected override OverlayStreamItem<APIUpdateStream> CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value); protected override OverlayStreamItem<APIUpdateStream> CreateStreamItem(APIUpdateStream value) => new ChangelogUpdateStreamItem(value);
protected override void LoadComplete()
{
// suppress base logic of immediately selecting first item if one exists
// (we always want to start with no stream selected).
}
} }
} }