Skip to content

Commit f9a7dab

Browse files
committed
Adds diff summary and failure diagnostics to structured diff
1 parent b92908b commit f9a7dab

12 files changed

+188
-28
lines changed

KustoSchemaTools/Changes/ClusterChanges.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static ClusterChangeSet GenerateChanges(Cluster oldCluster, Cluster newCl
4141
// Run Kusto code diagnostics
4242
foreach (var script in changeSet.Scripts)
4343
{
44-
var code = KustoCode.Parse(script.Text);
44+
var code = KustoCode.Parse(script.Script.Text);
4545
var diagnostics = code.GetDiagnostics();
4646
script.IsValid = !diagnostics.Any();
4747
}
@@ -244,7 +244,7 @@ private static string GenerateClusterMarkdown(ClusterChangeSet changeSet)
244244
sb.AppendLine("```kql");
245245
foreach (var script in changeSet.Scripts)
246246
{
247-
sb.AppendLine(script.Text);
247+
sb.AppendLine(script.Script.Text);
248248
sb.AppendLine();
249249
}
250250
sb.AppendLine("```");

KustoSchemaTools/Changes/DatabaseChanges.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ .. GenerateFollowerCachingChanges(oldState, newState, db => db.MaterializedViews
261261

262262
foreach(var script in result.SelectMany(itm => itm.Scripts))
263263
{
264-
var code = KustoCode.Parse(script.Text);
264+
var code = KustoCode.Parse(script.Script.Text);
265265
var diagnostics = code.GetDiagnostics();
266266
script.IsValid = diagnostics.Any() == false;
267267
}

KustoSchemaTools/Changes/DatabaseScriptContainer.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using KustoSchemaTools.Model;
1+
using System.Collections.Generic;
2+
using KustoSchemaTools.Model;
23
using Newtonsoft.Json;
34

45
namespace KustoSchemaTools.Changes
@@ -33,13 +34,10 @@ public DatabaseScriptContainer(string kind, int order, string script, bool isAsy
3334
[JsonProperty("isValid")]
3435
public bool? IsValid { get; set; }
3536

36-
[JsonProperty("text")]
37-
public string Text => Script.Text;
38-
39-
[JsonProperty("order")]
40-
public int Order => Script.Order;
41-
4237
[JsonProperty("isAsync")]
4338
public bool IsAsync { get; set; }
39+
40+
[JsonProperty("diagnostics", NullValueHandling = NullValueHandling.Ignore)]
41+
public List<ScriptDiagnostic>? Diagnostics { get; set; }
4442
}
4543
}

KustoSchemaTools/Changes/DeletionChange.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public List<DatabaseScriptContainer> Scripts
2222
get
2323
{
2424
var sc = new DatabaseScriptContainer("Deletion", 0, $".drop {EntityType} {Entity}");
25-
var code = KustoCode.Parse(sc.Text);
25+
var code = KustoCode.Parse(sc.Script.Text);
2626
var diagnostics = code.GetDiagnostics();
2727
sc.IsValid = diagnostics.Any() == false;
2828
return new List<DatabaseScriptContainer> { sc };

KustoSchemaTools/Changes/EntityGroupChange.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ private void Init()
3434
}
3535

3636
Scripts.Add(toScript);
37-
var code = KustoCode.Parse(toScript.Text);
37+
var code = KustoCode.Parse(toScript.Script.Text);
3838
var diagnostics = code.GetDiagnostics();
3939
toScript.IsValid = diagnostics.Any() == false;
4040
var logo = toScript.IsValid.Value ? ":green_circle:" : ":red_circle:";

KustoSchemaTools/Changes/ScriptCompareChange.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Data;
88
using DiffPlex.DiffBuilder.Model;
99
using Kusto.Language.Editor;
10+
using System.Linq;
1011

1112
namespace KustoSchemaTools.Changes
1213
{
@@ -32,8 +33,8 @@ private void Init()
3233
foreach (var change in to)
3334
{
3435
var before = from.ContainsKey(change.Kind) ? from[change.Kind] : null;
35-
var beforeText = before?.Text ?? "";
36-
var afterText = change.Text;
36+
var beforeText = before?.Script.Text ?? string.Empty;
37+
var afterText = change.Script.Text;
3738

3839
var singleLinebeforeText = new KustoCodeService(KustoCode.Parse(beforeText)).GetMinimalText(MinimalTextKind.SingleLine);
3940
var singleLineafterText = new KustoCodeService(KustoCode.Parse(afterText)).GetMinimalText(MinimalTextKind.SingleLine);
@@ -51,10 +52,19 @@ private void Init()
5152
var diff = InlineDiffBuilder.Diff(beforeText, afterText, true);
5253
if (diff.Lines.All(itm => itm.Type == ChangeType.Unchanged)) continue;
5354

54-
var code = KustoCode.Parse(change.Text);
55+
var code = KustoCode.Parse(change.Script.Text);
5556

5657
var diagnostics = code.GetDiagnostics();
57-
change.IsValid = diagnostics.Any() == false || change.Order == -1;
58+
var hasDiagnostics = diagnostics.Any();
59+
change.IsValid = hasDiagnostics == false || change.Script.Order == -1;
60+
change.Diagnostics = hasDiagnostics
61+
? diagnostics.Select(diagnostic => new ScriptDiagnostic
62+
{
63+
Start = diagnostic.Start,
64+
End = diagnostic.End,
65+
Description = diagnostic.Description
66+
}).ToList()
67+
: null;
5868
Scripts.Add(change);
5969

6070

@@ -102,7 +112,7 @@ private void Init()
102112
}
103113
sb.AppendLine("<tr>");
104114
sb.AppendLine($" <td colspan=\"2\">Script:</td>");
105-
sb.AppendLine($" <td colspan=\"10\"><pre lang=\"kql\">{change.Text.PrettifyKql()}</pre></td>");
115+
sb.AppendLine($" <td colspan=\"10\"><pre lang=\"kql\">{change.Script.Text.PrettifyKql()}</pre></td>");
106116
sb.AppendLine("</tr>");
107117

108118
if (change.IsValid == false)

KustoSchemaTools/Changes/StructuredChangeExtensions.cs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Text;
5+
using DiffPlex;
6+
using DiffPlex.DiffBuilder;
7+
using DiffPlex.DiffBuilder.Model;
48
using KustoSchemaTools.Model;
59

610
namespace KustoSchemaTools.Changes
@@ -36,6 +40,7 @@ public static StructuredChange ToStructuredChange(this IChange change)
3640
case ScriptCompareChange scriptCompare:
3741
structuredChange.ChangeType = scriptCompare.From == null ? "Create" : "Update";
3842
structuredChange.ScriptComparison = scriptCompare.ToStructuredScriptComparison();
43+
structuredChange.DiffMarkdown = BuildDiffMarkdown(scriptCompare);
3944
break;
4045
default:
4146
structuredChange.ChangeType = "Update";
@@ -69,6 +74,12 @@ public static StructuredChange ToStructuredChange(this IChange change)
6974
comparison.OldScripts.Add(CloneScript(previous));
7075
}
7176
}
77+
78+
var validationPayload = BuildValidationPayload(previousScripts, comparison.NewScripts);
79+
if (validationPayload.Count > 0)
80+
{
81+
comparison.ValidationResults = validationPayload;
82+
}
7283
}
7384

7485
foreach (var script in comparison.OldScripts.Where(s => !s.IsValid.HasValue))
@@ -86,7 +97,129 @@ private static DatabaseScriptContainer CloneScript(DatabaseScriptContainer sourc
8697
IsValid = source.IsValid
8798
};
8899

100+
if (source.Diagnostics != null && source.Diagnostics.Count > 0)
101+
{
102+
clone.Diagnostics = source.Diagnostics
103+
.Select(diagnostic => new ScriptDiagnostic
104+
{
105+
Start = diagnostic.Start,
106+
End = diagnostic.End,
107+
Description = diagnostic.Description
108+
})
109+
.ToList();
110+
}
111+
89112
return clone;
90113
}
114+
115+
private static Dictionary<string, object?> BuildValidationPayload(Dictionary<string, DatabaseScriptContainer> previousScripts, List<DatabaseScriptContainer> newScripts)
116+
{
117+
var payload = new Dictionary<string, object?>();
118+
foreach (var script in newScripts)
119+
{
120+
var oldScript = previousScripts.ContainsKey(script.Kind) ? previousScripts[script.Kind] : null;
121+
var diffPreview = BuildDiffPreview(oldScript, script);
122+
if (diffPreview.Count > 0)
123+
{
124+
var keyName = string.IsNullOrWhiteSpace(script.Kind) ? "diff" : $"diff::{script.Kind}";
125+
payload[keyName] = diffPreview;
126+
}
127+
}
128+
129+
return payload;
130+
}
131+
132+
private static List<string> BuildDiffPreview(DatabaseScriptContainer? oldScript, DatabaseScriptContainer? newScript)
133+
{
134+
var before = GetScriptText(oldScript);
135+
var after = GetScriptText(newScript);
136+
137+
if (string.Equals(before, after, StringComparison.Ordinal))
138+
{
139+
return new List<string>();
140+
}
141+
142+
var differ = new Differ();
143+
var diff = InlineDiffBuilder.Diff(before, after, false);
144+
145+
var preview = diff.Lines
146+
.Where(line => line.Type != ChangeType.Unchanged)
147+
.Select(line =>
148+
{
149+
var prefix = line.Type switch
150+
{
151+
ChangeType.Inserted => "+",
152+
ChangeType.Deleted => "-",
153+
ChangeType.Modified => "~",
154+
_ => " "
155+
};
156+
return $"{prefix}{line.Text?.TrimEnd()}";
157+
})
158+
.Where(line => !string.IsNullOrWhiteSpace(line))
159+
.Take(10)
160+
.ToList();
161+
162+
return preview;
163+
}
164+
165+
private static string GetScriptText(DatabaseScriptContainer? script)
166+
{
167+
return script?.Script?.Text ?? string.Empty;
168+
}
169+
170+
private static string? BuildDiffMarkdown(ScriptCompareChange change)
171+
{
172+
var previousScripts = change.From?
173+
.CreateScripts(change.Entity, false)
174+
.GroupBy(script => script.Kind)
175+
.Select(group => group.First())
176+
.ToDictionary(script => script.Kind, script => script)
177+
?? new Dictionary<string, DatabaseScriptContainer>();
178+
179+
var sb = new StringBuilder();
180+
var differ = new Differ();
181+
foreach (var script in change.Scripts)
182+
{
183+
var before = previousScripts.TryGetValue(script.Kind, out var prior)
184+
? prior.Script?.Text ?? string.Empty
185+
: string.Empty;
186+
var after = script.Script?.Text ?? string.Empty;
187+
188+
if (string.Equals(before, after, StringComparison.Ordinal))
189+
{
190+
continue;
191+
}
192+
193+
var diff = InlineDiffBuilder.Diff(before, after, false);
194+
var hasMeaningfulDiff = diff.Lines.Any(line => line.Type != ChangeType.Unchanged);
195+
if (!hasMeaningfulDiff)
196+
{
197+
continue;
198+
}
199+
200+
if (!string.IsNullOrWhiteSpace(script.Kind))
201+
{
202+
sb.AppendLine($"// {script.Kind}");
203+
}
204+
205+
sb.AppendLine("```diff");
206+
foreach (var line in diff.Lines)
207+
{
208+
var prefix = line.Type switch
209+
{
210+
ChangeType.Inserted => "+",
211+
ChangeType.Deleted => "-",
212+
ChangeType.Modified => "~",
213+
_ => " "
214+
};
215+
sb.AppendLine($"{prefix}{line.Text}");
216+
}
217+
sb.AppendLine("```");
218+
sb.AppendLine();
219+
}
220+
221+
var diffContent = sb.ToString().Trim();
222+
return string.IsNullOrEmpty(diffContent) ? null : diffContent;
223+
}
91224
}
92225
}

KustoSchemaTools/KustoSchemaHandler.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,9 @@ private async Task<DiffComputationResult> BuildDiffComputationResult(string path
183183
if (logDetails)
184184
{
185185
var scriptSb = new StringBuilder();
186-
foreach (var script in changes.SelectMany(itm => itm.Scripts).Where(itm => itm.IsValid == true).OrderBy(itm => itm.Order))
186+
foreach (var script in changes.SelectMany(itm => itm.Scripts).Where(itm => itm.IsValid == true).OrderBy(itm => itm.Script.Order))
187187
{
188-
scriptSb.AppendLine(script.Text);
188+
scriptSb.AppendLine(script.Script.Text);
189189
}
190190

191191
Log.LogInformation($"Following scripts will be applied:\n{scriptSb}");
@@ -226,7 +226,7 @@ private StructuredDiff ConvertToStructuredDiff(string clusterName, string cluste
226226
var validScripts = changes
227227
.SelectMany(change => change.Scripts)
228228
.Where(script => script.IsValid == true)
229-
.OrderBy(script => script.Order)
229+
.OrderBy(script => script.Script.Order)
230230
.ToList();
231231

232232
return new StructuredDiff
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Newtonsoft.Json;
2+
3+
namespace KustoSchemaTools.Model
4+
{
5+
public class ScriptDiagnostic
6+
{
7+
[JsonProperty("start")]
8+
public int Start { get; set; }
9+
10+
[JsonProperty("end")]
11+
public int End { get; set; }
12+
13+
[JsonProperty("description")]
14+
public string Description { get; set; } = string.Empty;
15+
}
16+
}

KustoSchemaTools/Model/StructuredDiff.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ public class StructuredChange
6565

6666
[JsonProperty("headingText", NullValueHandling = NullValueHandling.Ignore)]
6767
public string? HeadingText { get; set; }
68+
69+
[JsonProperty("diffMarkdown", NullValueHandling = NullValueHandling.Ignore)]
70+
public string? DiffMarkdown { get; set; }
6871
}
6972

7073
public class StructuredScriptComparison

0 commit comments

Comments
 (0)