Доработан билдер BPMN
This commit is contained in:
@@ -54,7 +54,7 @@ namespace SQLLinter.CLI
|
||||
|
||||
var diagramer = new Diagramer(bpmn, fragmentBuilder, sqlStreamReaderBuilder);
|
||||
|
||||
using (StreamReader reader = new StreamReader(@"C:\Users\frost\Downloads\Telegram Desktop\tdostdetail.sql"))
|
||||
using (StreamReader reader = new StreamReader(@"C:\Users\frost\Downloads\Telegram Desktop\test.sql"))
|
||||
{
|
||||
linter.Run("test.sql", reader.BaseStream);
|
||||
diagramer.Run("test.sql", reader.BaseStream);
|
||||
|
||||
12
SQLLinter/Infrastructure/Diagram/BpmnArrowType.cs
Normal file
12
SQLLinter/Infrastructure/Diagram/BpmnArrowType.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
/// <summary>
|
||||
/// Типы связей между узлами BPMN
|
||||
/// </summary>
|
||||
public enum BpmnArrowType
|
||||
{
|
||||
/// <summary> Обычная связь </summary>
|
||||
Default,
|
||||
/// <summary> Пунктирная связь (например, для вызова подпроцесса) </summary>
|
||||
Dashed,
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
13
SQLLinter/Infrastructure/Diagram/BpmnDiagram.cs
Normal file
13
SQLLinter/Infrastructure/Diagram/BpmnDiagram.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
/// <summary>
|
||||
/// Диаграмма BPMN, объединяющая процессы
|
||||
/// </summary>
|
||||
public class BpmnDiagram
|
||||
{
|
||||
/// <summary> Процессы диаграммы </summary>
|
||||
public List<BpmnProcess> Processes { get; set; } = new();
|
||||
|
||||
/// <summary> Глобальные связи между процессами </summary>
|
||||
public List<BpmnEdge> GlobalEdges { get; set; } = new();
|
||||
}
|
||||
34
SQLLinter/Infrastructure/Diagram/BpmnDiagramExtensions.cs
Normal file
34
SQLLinter/Infrastructure/Diagram/BpmnDiagramExtensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
/// <summary>
|
||||
/// Расширения для диаграммы BPMN
|
||||
/// </summary>
|
||||
public static class BpmnDiagramExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Добавления отсутсвующих связей между процессами и их подпроцессами
|
||||
/// </summary>
|
||||
/// <param name="diagram"></param>
|
||||
public static void AddMissingProcessEdges(this BpmnDiagram diagram)
|
||||
{
|
||||
foreach (var subprocess in diagram.Processes
|
||||
.SelectMany(p => p.Nodes.Select(n => new { node = n, procId = p.Id }))
|
||||
.Where(n => n.node.Type == BpmnNodeType.Subprocess && !string.IsNullOrEmpty(n.node.SubprocessId) && n.node.SubprocessId != n.procId)
|
||||
.Select(n => n.node)
|
||||
)
|
||||
{
|
||||
if (diagram.Processes.Any(p => p.Id == subprocess.SubprocessId)
|
||||
&& !diagram.GlobalEdges.Any(t => t.From == subprocess.Id && t.To == subprocess.SubprocessId)
|
||||
)
|
||||
{
|
||||
// Добавить пунктирный край от узла подпроцесса к началу подпроцесса
|
||||
diagram.GlobalEdges.Add(new BpmnEdge
|
||||
{
|
||||
From = subprocess.Id,
|
||||
To = subprocess.SubprocessId + "_start",
|
||||
ArrowType = BpmnArrowType.Dashed,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
SQLLinter/Infrastructure/Diagram/BpmnEdge.cs
Normal file
19
SQLLinter/Infrastructure/Diagram/BpmnEdge.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
/// <summary>
|
||||
/// Связь между узлами BPMN
|
||||
/// </summary>
|
||||
public class BpmnEdge
|
||||
{
|
||||
/// <summary> Идентификатор узла-источника </summary>
|
||||
public string From { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> Идентификатор узла-приемника </summary>
|
||||
public string To { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> Метка/название связи </summary>
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> Тип стрелки связи </summary>
|
||||
public BpmnArrowType ArrowType { get; set; } = BpmnArrowType.Default;
|
||||
}
|
||||
22
SQLLinter/Infrastructure/Diagram/BpmnNode.cs
Normal file
22
SQLLinter/Infrastructure/Diagram/BpmnNode.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
/// <summary>
|
||||
/// Узел BPMN
|
||||
/// </summary>
|
||||
public class BpmnNode
|
||||
{
|
||||
///<summary> Уникальный идентификатор узла </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
///<summary> Метка/название узла </summary>
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
///<summary> Тип узла </summary>
|
||||
public BpmnNodeType Type { get; set; }
|
||||
|
||||
/// <summary> Идентификатор подпроцесса (если тип узла - Subprocess) </summary>
|
||||
public string SubprocessId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> Дополнительные свойства узла </summary>
|
||||
public Dictionary<string, string> Properties { get; set; } = new();
|
||||
}
|
||||
20
SQLLinter/Infrastructure/Diagram/BpmnNodeType.cs
Normal file
20
SQLLinter/Infrastructure/Diagram/BpmnNodeType.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
/// <summary>
|
||||
/// Типы узлов BPMN
|
||||
/// </summary>
|
||||
public enum BpmnNodeType
|
||||
{
|
||||
/// <summary> Стартовый узел процесса </summary>
|
||||
Start,
|
||||
/// <summary> Конечный узел процесса </summary>
|
||||
End,
|
||||
/// <summary> Задача </summary>
|
||||
Task,
|
||||
/// <summary> Шлюз (разветвление/объединение) </summary>
|
||||
Gateway,
|
||||
/// <summary> Шлюз (разветвление/объединение) </summary>
|
||||
Hexagon,
|
||||
/// <summary> Подпроцесс (вызов другого процесса) </summary>
|
||||
Subprocess,
|
||||
}
|
||||
19
SQLLinter/Infrastructure/Diagram/BpmnProcess.cs
Normal file
19
SQLLinter/Infrastructure/Diagram/BpmnProcess.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
/// <summary>
|
||||
/// Процесс BPMN
|
||||
/// </summary>
|
||||
public class BpmnProcess
|
||||
{
|
||||
/// <summary> Уникальный идентификатор процесса </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> Название процесса </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary> Узлы процесса </summary>
|
||||
public List<BpmnNode> Nodes { get; set; } = new();
|
||||
|
||||
/// <summary> Связи процесса </summary>
|
||||
public List<BpmnEdge> Edges { get; set; } = new();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace SQLLinter.Infrastructure.Diagram;
|
||||
|
||||
@@ -9,114 +9,72 @@ public static class MermaidRenderer
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("flowchart TB");
|
||||
|
||||
// main: print Start, then tasks/gateways, then End
|
||||
var mainStart = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.Start).ToList();
|
||||
var mainTasks = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.Task || n.Type == BpmnNodeType.Gateway).ToList();
|
||||
var mainEnd = diagram.Main.Nodes.Where(n => n.Type == BpmnNodeType.End).ToList();
|
||||
|
||||
foreach (var node in mainStart)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
foreach (var node in mainTasks)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
if (node.Type == BpmnNodeType.Gateway)
|
||||
sb.AppendLine($" {nid}{{\"{label}\"}}");
|
||||
else
|
||||
sb.AppendLine($" {nid}[\"{label}\"]");
|
||||
}
|
||||
foreach (var node in mainEnd)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
// subprocesses as subgraphs: pool label, start, tasks/gateways, end
|
||||
foreach (var proc in diagram.Subprocesses)
|
||||
// -----------------------------------------
|
||||
// Рендер каждого процесса как subgraph
|
||||
// -----------------------------------------
|
||||
foreach (var proc in diagram.Processes)
|
||||
{
|
||||
var procId = SanitizeId(proc.Id);
|
||||
sb.AppendLine($" subgraph {procId} [\"{Escape(proc.Name)}\"]");
|
||||
sb.AppendLine($" direction TB");
|
||||
sb.AppendLine(" direction TB");
|
||||
|
||||
var startNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Start).ToList();
|
||||
var taskNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Task || n.Type == BpmnNodeType.Gateway).ToList();
|
||||
var endNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.End).ToList();
|
||||
var poolNodes = proc.Nodes.Where(n => n.Type == BpmnNodeType.Subprocess).ToList();
|
||||
|
||||
// pool label nodes (usually first)
|
||||
foreach (var node in poolNodes)
|
||||
foreach (var node in proc.Nodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}[\"{label}\"]");
|
||||
|
||||
label = node.Type switch
|
||||
{
|
||||
BpmnNodeType.Start => $@"((""{label}""))",
|
||||
BpmnNodeType.Task => $@"[""{label}""]",
|
||||
BpmnNodeType.Subprocess => $@"[[""{label}""]]",
|
||||
BpmnNodeType.Gateway => $@"{{""{label}""}}",
|
||||
BpmnNodeType.Hexagon => $@"{{{{""{label}""}}}}",
|
||||
BpmnNodeType.End => $@"((""{label}""))",
|
||||
_ => $@">""{label}""]"
|
||||
};
|
||||
|
||||
sb.AppendLine($" {nid}{label}");
|
||||
}
|
||||
|
||||
foreach (var node in startNodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
foreach (var node in taskNodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
if (node.Type == BpmnNodeType.Gateway)
|
||||
sb.AppendLine($" {nid}{{\"{label}\"}}");
|
||||
else
|
||||
sb.AppendLine($" {nid}[\"{label}\"]");
|
||||
}
|
||||
foreach (var node in endNodes)
|
||||
{
|
||||
var nid = SanitizeId(node.Id);
|
||||
var label = Escape(node.Label);
|
||||
sb.AppendLine($" {nid}((\"{label}\"))");
|
||||
}
|
||||
|
||||
// edges inside subprocess
|
||||
// Edges внутри процесса
|
||||
foreach (var e in proc.Edges)
|
||||
{
|
||||
var from = SanitizeId(e.From);
|
||||
var to = SanitizeId(e.To);
|
||||
var arrow = e.Dashed ? "-.->" : "-->";
|
||||
var arrow = e.ArrowType switch
|
||||
{
|
||||
BpmnArrowType.Dashed => "-.->",
|
||||
_ => "-->",
|
||||
};
|
||||
var lbl = string.IsNullOrWhiteSpace(e.Label) ? string.Empty : $" |{Escape(e.Label)}|";
|
||||
sb.AppendLine($" {from} {arrow} {to}{lbl}");
|
||||
|
||||
sb.AppendLine($" {from} {arrow}{lbl} {to}");
|
||||
}
|
||||
|
||||
sb.AppendLine(" end");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
// main edges
|
||||
foreach (var e in diagram.Main.Edges)
|
||||
// -----------------------------------------
|
||||
// Рендер глобальных связей между процессами
|
||||
// -----------------------------------------
|
||||
foreach (var e in diagram.GlobalEdges)
|
||||
{
|
||||
var from = SanitizeId(e.From);
|
||||
var to = SanitizeId(e.To);
|
||||
var arrow = e.Dashed ? "-.->" : "-->";
|
||||
var arrow = e.ArrowType switch
|
||||
{
|
||||
BpmnArrowType.Dashed => "-.->",
|
||||
_ => "-->",
|
||||
};
|
||||
var lbl = string.IsNullOrWhiteSpace(e.Label) ? string.Empty : $" |{Escape(e.Label)}|";
|
||||
sb.AppendLine($" {from} {arrow} {to}{lbl}");
|
||||
sb.AppendLine($" {from} {arrow}{lbl} {to}");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string RenderMarkdown(BpmnDiagram diagram)
|
||||
{
|
||||
var content = ToMermaidContent(diagram);
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("```mermaid");
|
||||
sb.Append(content);
|
||||
sb.AppendLine("```");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string Escape(string s)
|
||||
{
|
||||
if (s == null) return string.Empty;
|
||||
@@ -134,4 +92,4 @@ public static class MermaidRenderer
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,7 @@ public class SqlDiagramProcessor : ISqlDiagramProcessor
|
||||
return;
|
||||
}
|
||||
|
||||
var diagramm = BpmnBuilder.Build(fragment);
|
||||
_bpmnDiagram.Subprocesses.Add(diagramm.Main);
|
||||
BpmnBuilder.Build(fragment, _bpmnDiagram);
|
||||
}
|
||||
|
||||
private Stream GetFileContents(string filePath)
|
||||
|
||||
@@ -93,8 +93,8 @@ class DiagramViewer {
|
||||
this.scale = 1;
|
||||
this.tx = 0;
|
||||
this.ty = 0;
|
||||
this.minScale = 0.1;
|
||||
this.maxScale = 6;
|
||||
this.minScale = 0.3;
|
||||
this.maxScale = 12;
|
||||
|
||||
this.originalViewBox = null;
|
||||
this.minimapSvg = null;
|
||||
|
||||
Reference in New Issue
Block a user