Skip to content

Commit 3c10f6d

Browse files
authored
add version number has to hx-get to break cache on version updates (#1423)
* add version number has to hx-get to break cache on version updates * update more places with hx-get and make append of querystring try to check for existing querystring * only set it on hx-get * update test cases * Add extension method class * Account for anchors when appending version hash * include anchor and querystring test on hx-get
1 parent 8f78099 commit 3c10f6d

File tree

9 files changed

+246
-37
lines changed

9 files changed

+246
-37
lines changed

src/Elastic.Documentation.Site/Htmx.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,74 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System.Reflection;
56
using System.Text;
7+
using System.Text.Encodings.Web;
8+
using Elastic.Documentation.Extensions;
69

710
namespace Elastic.Documentation.Site;
811

12+
public static class UrlHelper
13+
{
14+
private static readonly KeyValuePair<string, string?>[] VersionParameters = [new("v", Htmx.VersionHash)];
15+
16+
public static string AddVersionParameters(string uri) => AddQueryString(uri, VersionParameters);
17+
18+
/// <summary>
19+
/// Append the given query keys and values to the URI.
20+
/// </summary>
21+
/// <param name="uri">The base URI.</param>
22+
/// <param name="queryString">A collection of name value query pairs to append.</param>
23+
/// <returns>The combined result.</returns>
24+
/// <exception cref="ArgumentNullException"><paramref name="uri"/> is <c>null</c>.</exception>
25+
/// <exception cref="ArgumentNullException"><paramref name="queryString"/> is <c>null</c>.</exception>
26+
public static string AddQueryString(
27+
string uri,
28+
IEnumerable<KeyValuePair<string, string?>> queryString)
29+
{
30+
ArgumentNullException.ThrowIfNull(uri);
31+
ArgumentNullException.ThrowIfNull(queryString);
32+
33+
var anchorIndex = uri.IndexOf('#');
34+
var uriToBeAppended = uri.AsSpan();
35+
var anchorText = ReadOnlySpan<char>.Empty;
36+
// If there is an anchor, then the query string must be inserted before its first occurrence.
37+
if (anchorIndex != -1)
38+
{
39+
anchorText = uriToBeAppended.Slice(anchorIndex);
40+
uriToBeAppended = uriToBeAppended.Slice(0, anchorIndex);
41+
}
42+
43+
var queryIndex = uriToBeAppended.IndexOf('?');
44+
var hasQuery = queryIndex != -1;
45+
46+
var sb = new StringBuilder();
47+
_ = sb.Append(uriToBeAppended);
48+
foreach (var parameter in queryString)
49+
{
50+
if (parameter.Value == null)
51+
continue;
52+
53+
_ = sb.Append(hasQuery ? '&' : '?')
54+
.Append(UrlEncoder.Default.Encode(parameter.Key))
55+
.Append('=')
56+
.Append(UrlEncoder.Default.Encode(parameter.Value));
57+
hasQuery = true;
58+
}
59+
60+
_ = sb.Append(anchorText);
61+
return sb.ToString();
62+
}
63+
}
64+
965
public static class Htmx
1066
{
67+
private static readonly string Version =
68+
Assembly.GetExecutingAssembly().GetCustomAttributes<AssemblyInformationalVersionAttribute>()
69+
.FirstOrDefault()?.InformationalVersion ?? "0.0.0";
70+
71+
public static readonly string VersionHash = ShortId.Create(Version);
72+
1173
public static string GetHxSelectOob(bool hasSameTopLevelGroup) => hasSameTopLevelGroup ? "#content-container,#toc-nav" : "#main-container";
1274
public const string Preload = "mousedown";
1375
public const string HxSwap = "none";
@@ -24,8 +86,10 @@ public static string GetHxAttributes(
2486
string? hxIndicator = HxIndicator
2587
)
2688
{
89+
var hxGetUrl = UrlHelper.AddVersionParameters(targetUrl);
90+
2791
var attributes = new StringBuilder();
28-
_ = attributes.Append($" hx-get={targetUrl}");
92+
_ = attributes.Append($" hx-get={hxGetUrl}");
2993
_ = attributes.Append($" hx-select-oob={hxSwapOob ?? GetHxSelectOob(hasSameTopLevelGroup)}");
3094
_ = attributes.Append($" hx-swap={hxSwap}");
3195
_ = attributes.Append($" hx-push-url={hxPushUrl}");

src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public bool TryGetTableOfContentsTree(Uri source, [NotNullWhen(true)] out TableO
3737
}
3838

3939

40-
[DebuggerDisplay("Toc >{Depth} {FolderName} ({NavigationItems.Count} items)")]
40+
[DebuggerDisplay("Toc >{Depth} {FolderName} {Source} ({NavigationItems.Count} items)")]
4141
public class TableOfContentsTree : DocumentationGroup, IRootNavigationItem<MarkdownFile, INavigationItem>
4242
{
4343
public Uri Source { get; }

src/Elastic.Markdown/Myst/Renderers/HtmxLinkInlineRenderer.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information
44

5+
using System.Reflection;
6+
using Elastic.Documentation.Extensions;
57
using Elastic.Documentation.Site;
68
using Elastic.Documentation.Site.Navigation;
79
using Elastic.Markdown.IO;
@@ -25,7 +27,10 @@ protected override void Write(HtmlRenderer renderer, LinkInline link)
2527
return;
2628
}
2729

28-
var url = link.GetDynamicUrl != null ? link.GetDynamicUrl() : link.Url;
30+
var url = link.GetDynamicUrl?.Invoke() ?? link.Url;
31+
var hxGetUrl = url;
32+
if (hxGetUrl is not null)
33+
hxGetUrl = UrlHelper.AddVersionParameters(hxGetUrl);
2934

3035
var isCrossLink = (link.GetData("isCrossLink") as bool?) == true;
3136
var isHttpLink = url?.StartsWith("http") ?? false;
@@ -41,7 +46,7 @@ protected override void Write(HtmlRenderer renderer, LinkInline link)
4146
var targetRootNavigation = link.GetData($"Target{nameof(MarkdownFile.NavigationRoot)}") as INodeNavigationItem<INavigationModel, INavigationItem>;
4247
var hasSameTopLevelGroup = !isCrossLink && (currentRootNavigation?.Id == targetRootNavigation?.Id);
4348
_ = renderer.Write(" hx-get=\"");
44-
_ = renderer.WriteEscapeUrl(url);
49+
_ = renderer.WriteEscapeUrl(hxGetUrl);
4550
_ = renderer.Write('"');
4651
_ = renderer.Write($" hx-select-oob=\"{Htmx.GetHxSelectOob(hasSameTopLevelGroup)}\"");
4752
_ = renderer.Write($" hx-swap=\"{Htmx.HxSwap}\"");

tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17+
<PackageReference Include="DiffPlex" />
1718
<PackageReference Include="Microsoft.NET.Test.Sdk" />
1819
<PackageReference Include="xunit.v3" />
1920
<PackageReference Include="xunit.runner.visualstudio" />
2021
<PackageReference Include="GitHubActionsTestLogger" />
22+
<PackageReference Include="AngleSharp.Diffing" />
2123

2224
<PackageReference Include="FluentAssertions" />
2325
<PackageReference Include="JetBrains.Annotations" />

tests/Elastic.Markdown.Tests/Inline/AnchorLinkTests.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,15 @@ [Sub Requirements](testing/req.md#sub-requirements)
7474
[Fact]
7575
public void GeneratesHtml() =>
7676
// language=html
77-
Html.Should().Contain(
77+
Html.ShouldContainHtml(
7878
"""<p><a href="/docs/testing/req#sub-requirements" hx-get="/docs/testing/req#sub-requirements" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Sub Requirements</a></p>"""
7979
);
8080

81+
[Fact]
82+
public void HxGetContainsVersionAnchor() =>
83+
// language=html
84+
Html.Should().MatchRegex("""hx-get="/docs/testing/req\?v=(.+?)#sub-requirements""");
85+
8186
[Fact]
8287
public void HasNoErrors() => Collector.Diagnostics.Should().HaveCount(0);
8388
}
@@ -92,7 +97,7 @@ [Sub Requirements](testing/req.md#new-reqs)
9297
[Fact]
9398
public void GeneratesHtml() =>
9499
// language=html
95-
Html.Should().Contain(
100+
Html.ShouldContainHtml(
96101
"""<p><a href="/docs/testing/req#new-reqs" hx-get="/docs/testing/req#new-reqs" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Sub Requirements</a></p>"""
97102
);
98103

@@ -108,8 +113,7 @@ public class ExternalPageAnchorAutoTitleTests(ITestOutputHelper output) : Anchor
108113
{
109114
[Fact]
110115
public void GeneratesHtml() =>
111-
// language=html
112-
Html.Should().Contain(
116+
Html.ShouldContainHtml(
113117
"""<p><a href="/docs/testing/req#sub-requirements" hx-get="/docs/testing/req#sub-requirements" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Special Requirements &gt; Sub Requirements</a></p>"""
114118
);
115119

@@ -126,8 +130,7 @@ public class InPageBadAnchorTests(ITestOutputHelper output) : AnchorLinkTestBase
126130
{
127131
[Fact]
128132
public void GeneratesHtml() =>
129-
// language=html
130-
Html.Should().Contain(
133+
Html.ShouldContainHtml(
131134
"""<p><a href="#hello-world2">Hello</a></p>"""
132135
);
133136

@@ -144,8 +147,7 @@ [Sub Requirements](testing/req.md#sub-requirements2)
144147
{
145148
[Fact]
146149
public void GeneratesHtml() =>
147-
// language=html
148-
Html.Should().Contain(
150+
Html.ShouldContainHtml(
149151
"""<p><a href="/docs/testing/req#sub-requirements2" hx-get="/docs/testing/req#sub-requirements2" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Sub Requirements</a></p>"""
150152
);
151153

@@ -163,8 +165,7 @@ [Heading inside dropdown](testing/req.md#heading-inside-dropdown)
163165
{
164166
[Fact]
165167
public void GeneratesHtml() =>
166-
// language=html
167-
Html.Should().Contain(
168+
Html.ShouldContainHtml(
168169
"""<a href="/docs/testing/req#heading-inside-dropdown" hx-get="/docs/testing/req#heading-inside-dropdown" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Heading inside dropdown</a>"""
169170
);
170171
[Fact]

tests/Elastic.Markdown.Tests/Inline/DirectiveBlockLinkTests.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,8 @@ [Sub Requirements](testing/req.md#hint_ref)
6464
{
6565
[Fact]
6666
public void GeneratesHtml() =>
67-
// language=html
68-
Html.Should().Contain(
69-
"""<p><a href="/docs/testing/req#hint_ref" hx-get="/docs/testing/req#hint_ref" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Sub Requirements</a></p>"""
67+
Html.ShouldContainHtml(
68+
"""<p><a href="/docs/testing/req#hint_ref">Sub Requirements</a></p>"""
7069
);
7170

7271
[Fact]

tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,7 @@ [Sub Requirements](testing/req.md#custom-anchor)
198198
{
199199
[Fact]
200200
public void GeneratesHtml() =>
201-
// language=html
202-
Html.Should().Contain(
201+
Html.ShouldContainHtml(
203202
"""<p><a href="/docs/testing/req#custom-anchor" hx-get="/docs/testing/req#custom-anchor" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Sub Requirements</a></p>"""
204203
);
205204

tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ public class InlineLinkTests(ITestOutputHelper output) : LinkTestBase(output,
4747
{
4848
[Fact]
4949
public void GeneratesHtml() =>
50-
// language=html
51-
Html.Should().Be(
50+
Html.ShouldContainHtml(
5251
"""<p><a href="/docs/_static/img/observability.png" hx-get="/docs/_static/img/observability.png" hx-select-oob="#main-container" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Elasticsearch</a></p>"""
5352
);
5453

@@ -64,8 +63,7 @@ public class LinkToPageTests(ITestOutputHelper output) : LinkTestBase(output,
6463
{
6564
[Fact]
6665
public void GeneratesHtml() =>
67-
// language=html
68-
Html.Should().Contain(
66+
Html.ShouldContainHtml(
6967
"""<p><a href="/docs/testing/req" hx-get="/docs/testing/req" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Requirements</a></p>"""
7068
);
7169

@@ -84,8 +82,7 @@ public class InsertPageTitleTests(ITestOutputHelper output) : LinkTestBase(outpu
8482
{
8583
[Fact]
8684
public void GeneratesHtml() =>
87-
// language=html
88-
Html.Should().Contain(
85+
Html.ShouldContainHtml(
8986
"""<p><a href="/docs/testing/req" hx-get="/docs/testing/req" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Special Requirements</a></p>"""
9087
);
9188

@@ -106,8 +103,7 @@ public class RepositoryLinksTest(ITestOutputHelper output) : LinkTestBase(output
106103
{
107104
[Fact]
108105
public void GeneratesHtml() =>
109-
// language=html
110-
Html.Should().Contain(
106+
Html.ShouldContainHtml(
111107
"""<p><a href="/docs/testing/req" hx-get="/docs/testing/req" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">test</a></p>"""
112108
);
113109

@@ -266,16 +262,16 @@ public class CommentedNonExistingLinks2(ITestOutputHelper output) : LinkTestBase
266262
{
267263
[Fact]
268264
public void GeneratesHtml() =>
269-
// language=html
270-
Html.ReplaceLineEndings().TrimEnd().Should().Be("""
271-
<p>Links:</p>
272-
<ul>
273-
<li><a href="/docs/testing/req" hx-get="/docs/testing/req" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Special Requirements</a></li>
274-
</ul>
275-
<ul>
276-
<li><a href="/docs/testing/req" hx-get="/docs/testing/req" hx-select-oob="#content-container,#toc-nav" hx-swap="none" hx-push-url="true" hx-indicator="#htmx-indicator" preload="mousedown">Special Requirements</a></li>
277-
</ul>
278-
""".ReplaceLineEndings());
265+
Html.ShouldMatchHtml(
266+
"""
267+
<p>Links:</p>
268+
<ul>
269+
<li><a href="/docs/testing/req">Special Requirements</a></li>
270+
</ul>
271+
<ul>
272+
<li><a href="/docs/testing/req">Special Requirements</a></li>
273+
</ul>
274+
""");
279275

280276
[Fact]
281277
public void HasErrors() => Collector.Diagnostics.Should().HaveCount(0);

0 commit comments

Comments
 (0)