diff --git a/CHANGELOG.md b/CHANGELOG.md
index bfef116d43..9b09bb0388 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,7 @@ All notable changes to this project will be documented in this file.
- [Multiple] Allow YAML for human-edited metadata (YAMLKAN) (#3367 by: HebaruSan; reviewed: DasSkelett)
- [Netkan] Fill more info from GitHub for SpaceDock mods (#3390 by: HebaruSan; reviewed: DasSkelett)
- [Spec] YAMLize netkan spec (#3438 by: HebaruSan; reviewed: DasSkelett)
+- [Netkan] Append resource links to staging PRs (#3454 by: HebaruSan; reviewed: techman83)
## v1.30.4 (Hubble)
diff --git a/Netkan/CKAN-netkan.csproj b/Netkan/CKAN-netkan.csproj
index 5e90816999..0c8f07a105 100644
--- a/Netkan/CKAN-netkan.csproj
+++ b/Netkan/CKAN-netkan.csproj
@@ -121,6 +121,7 @@
+
diff --git a/Netkan/Transformers/NetkanTransformer.cs b/Netkan/Transformers/NetkanTransformer.cs
index 7cdaedf6d4..7a85e6c619 100644
--- a/Netkan/Transformers/NetkanTransformer.cs
+++ b/Netkan/Transformers/NetkanTransformer.cs
@@ -43,6 +43,7 @@ IValidator validator
new AvcKrefTransformer(http, ghApi),
new InternalCkanTransformer(http, moduleService),
new AvcTransformer(http, moduleService, ghApi),
+ new StagingLinksTransformer(),
new LocalizationsTransformer(http, moduleService),
new VersionEditTransformer(),
new ForcedVTransformer(),
diff --git a/Netkan/Transformers/StagingLinksTransformer.cs b/Netkan/Transformers/StagingLinksTransformer.cs
new file mode 100644
index 0000000000..7d926ad6fb
--- /dev/null
+++ b/Netkan/Transformers/StagingLinksTransformer.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using log4net;
+using Newtonsoft.Json.Linq;
+
+using CKAN.NetKAN.Model;
+
+namespace CKAN.NetKAN.Transformers
+{
+ internal sealed class StagingLinksTransformer : ITransformer
+ {
+ public string Name { get { return "staging_links"; } }
+
+ public IEnumerable Transform(Metadata metadata, TransformOptions opts)
+ {
+ if (opts.Staged && !string.IsNullOrEmpty(opts.StagingReason))
+ {
+ var table = LinkTable(metadata);
+ if (!string.IsNullOrEmpty(table))
+ {
+ log.DebugFormat("Adding links to staging reason: {0}",
+ table);
+ opts.StagingReason += table;
+ }
+ }
+ // This transformer never changes the metadata
+ yield return metadata;
+ }
+
+ private string LinkTable(Metadata metadata)
+ {
+ var resourcesJson = (JObject)metadata?.Json()?["resources"];
+ if (resourcesJson == null)
+ {
+ // No resources, no links to append
+ return "";
+ }
+ StringBuilder table = new StringBuilder();
+ // Blank lines to separate the table from the description
+ table.AppendLine("");
+ table.AppendLine("");
+ table.AppendLine("Resource | URL");
+ table.AppendLine(":-- | :--");
+ foreach (var prop in resourcesJson.Properties().OrderBy(prop => prop.Name))
+ {
+ table.AppendLine($"{prop.Name} | <{prop.Value}>");
+ }
+ return table.ToString();
+ }
+
+ private static readonly ILog log = LogManager.GetLogger(typeof(StagingLinksTransformer));
+ }
+}