GitLab MR Diagram Pairing¶
Tài liệu này mô tả cách browser extension nhận diện và ghép diagram giữa hai phiên bản Markdown Before và After trong GitLab Merge Request.
- Thuật toán ghép cặp:
browser-extension/src/diagram-parser.js. - Thuật toán đặt nút vào GitLab diff:
browser-extension/src/diff-locator.js. - Ghép cặp và đặt nút là hai giai đoạn độc lập.
1. Vì sao cần stable diagram ID?¶
Nếu chỉ dựa vào engine, title hoặc thứ tự, extension có thể ghép nhầm khi người dùng:
- di chuyển diagram sang vị trí khác;
- đổi heading hoặc title;
- thêm một diagram vào giữa nhiều diagram cùng engine;
- đổi renderer, ví dụ PlantUML sang BlockDiag.
diagram-id tạo một định danh ổn định, sống cùng diagram qua các lần chỉnh sửa:
```plantuml diagram-id=payment-flow {theme=dark scale=2}
@startuml
Customer -> PaymentService
@enduml
```
ID nằm ngoài {...} vì {...} là rendering directive. diagram-id chỉ phục vụ ghép cặp và không
được gửi tới rendering server.
2. Quy tắc của diagram ID¶
| Quy tắc | Cách xử lý |
|---|---|
| Độ dài | 1–64 ký tự |
| Ký tự hợp lệ | a-z, 0-9, ., _, - |
| Ký tự đầu tiên | Chữ hoặc số |
| Chữ hoa/chữ thường | Chuẩn hóa thành lowercase |
| Phạm vi duy nhất | Trong một file Markdown, xét riêng từng phía Before/After |
| Có bắt buộc không? | Không; Markdown cũ vẫn dùng legacy matching |
Ví dụ, diagram-id=Payment-Flow được chuẩn hóa thành payment-flow.
Mỗi diagram nhận một trạng thái ID:
Một ID xuất hiện một lần ở Before và một lần ở After là bình thường. Nó chỉ bị coi là duplicate khi xuất hiện nhiều lần trong cùng phía của cùng file.
3. Dữ liệu nhận diện một diagram¶
Parser tạo một bản ghi cho mỗi fenced code block được hỗ trợ:
| Trường | Ý nghĩa |
|---|---|
diagramId |
ID đã trim và lowercase |
diagramIdStatus |
missing, valid, invalid hoặc duplicate |
engine |
Renderer sau khi chuẩn hóa alias, ví dụ puml thành plantuml |
title |
Tên hiển thị lấy từ metadata, heading hoặc source |
identity |
Legacy identity gồm engine + title đã chuẩn hóa |
source |
Source nguyên bản dùng để render |
comparisonSource |
Source đã chuẩn hóa tối thiểu để so sánh |
startLine, endLine |
Khoảng dòng dùng để đặt action trong GitLab diff |
Title được chọn theo thứ tự ưu tiên:
4. Thuật toán ghép cặp¶
Extension chạy ba vòng ghép theo độ tin cậy giảm dần.
Vòng 1 — Ghép bằng stable ID¶
Một cặp được tạo khi:
- ID ở Before có trạng thái
valid; - ID ở After có trạng thái
valid; - hai ID giống nhau sau khi lowercase.
Kết quả có pairing = id và không phụ thuộc vào title, vị trí hoặc engine.
Vòng 2 — Ghép bằng legacy identity¶
Các diagram chưa được ghép ở vòng 1 có thể ghép bằng engine + title khi identity đó xuất hiện đúng
một lần ở mỗi phía.
Vòng này phục vụ:
- Markdown cũ chưa có ID;
- PR baseline mới thêm ID ở một phía;
- duplicate/invalid ID nhưng vẫn còn title duy nhất để nhận diện an toàn.
Hai diagram đều có ID valid nhưng khác nhau bị chặn ở vòng này, kể cả khi title và engine giống hệt.
Vòng 3 — Ghép theo engine và thứ tự¶
Các diagram còn lại có thể ghép với diagram chưa dùng đầu tiên có cùng engine.
Occurrence fallback bị cấm khi một trong hai diagram có ID duplicate hoặc invalid, hoặc khi cả hai
có ID valid. Nếu có nhiều ứng viên cùng engine ở một phía, kết quả được đánh dấu ambiguous và UI
hiện cảnh báo.
5. Cây quyết định cho một diagram After¶
Sau khi xử lý hết After, mọi diagram Before chưa được ghép trở thành removed.
6. Các case và kết quả¶
| Case | Before | After | Kết quả |
|---|---|---|---|
| 1. Cùng ID, source không đổi | valid: flow |
valid: flow |
Ghép bằng ID, unchanged |
| 2. Cùng ID, source đổi | valid: flow |
valid: flow |
Ghép bằng ID, changed |
| 3. Cùng ID, đổi vị trí/title | valid: flow |
valid: flow |
Vẫn là một cặp |
| 4. Cùng ID, đổi engine | PlantUML flow |
BlockDiag flow |
Vẫn ghép bằng ID; panel ghi rõ hai engine khi có diff |
| 5. Hai valid ID khác nhau | flow-v1 |
flow-v2 |
Không fallback: một added, một removed |
| 6. Chỉ một phía có ID | Không ID | valid: flow |
Legacy fallback; migration = true nếu ghép được |
| 7. Duplicate ID, title duy nhất | Hai duplicate: flow có title khác nhau |
Tương tự | Có thể ghép theo unique legacy title và hiện cảnh báo |
| 8. Duplicate ID, không có title duy nhất | Nhiều duplicate: flow |
Nhiều duplicate: flow |
Không occurrence-pair; tách added/removed |
| 9. Invalid ID, title duy nhất | ID sai định dạng | ID sai định dạng | Có thể legacy-pair theo unique title và hiện cảnh báo |
| 10. Không có ID, title duy nhất | Cùng engine/title | Cùng engine/title | Legacy identity pairing |
| 11. Không có ID/title, một diagram cùng engine | Diagram thứ nhất | Diagram thứ nhất | Occurrence pairing |
| 12. Không có ID/title, nhiều diagram cùng engine | Nhiều diagram | Nhiều diagram | Occurrence pairing với ambiguous = true |
| 13. Chỉ có ở After | Không tồn tại | Diagram mới | added |
| 14. Chỉ có ở Before | Diagram cũ | Không tồn tại | removed |
Case 1–4: Cùng stable ID¶
ID được ưu tiên trước mọi metadata legacy:
Đổi renderer không phá vỡ quan hệ cặp. Tuy nhiên, trạng thái changed/unchanged hiện được xác định từ
comparisonSource; vì vậy nếu chỉ đổi engine nhưng giữ source giống hệt, cặp vẫn đúng nhưng có thể được
phân loại unchanged và không hiện action diff.
Case 5: Hai valid ID khác nhau¶
Extension coi đổi ID là thay thế định danh, không phải đổi nội dung của cùng một diagram. Điều này ngăn title hoặc vị trí vô tình kéo hai diagram khác nhau vào cùng một cặp.
Case 6: Migration một phía có ID¶
Nếu PR vừa thêm ID vừa đổi engine, legacy identity và occurrence đều không thể ghép vì hai cơ chế này yêu cầu cùng engine. Để migration an toàn, nên tạo MR baseline chỉ thêm ID trước.
Case 7–8: Duplicate ID¶
Ví dụ lỗi:
## Login flow
```plantuml diagram-id=shared-flow
...
```
## Payment flow
```d2 diagram-id=shared-flow
...
```
Extension không tự chọn diagram đầu tiên mang ID trùng, vì lựa chọn đó có thể tạo diff sai nhưng trông vẫn hợp lệ. Người dùng phải sửa ID để khôi phục stable matching.
Case 10–12: Markdown cũ không có ID¶
Khả năng tương thích này giúp repository cũ tiếp tục hoạt động, nhưng không đảm bảo chính xác tuyệt đối khi reorder hoặc chèn diagram giữa nhiều diagram cùng engine.
7. Phân loại trạng thái sau khi ghép¶
Sau khi tạo cặp, extension so sánh comparisonSource:
comparisonSource chỉ chuẩn hóa:
CRLFthànhLF;- khoảng trắng cuối dòng;
- newline thừa ở cuối source.
Source nguyên bản vẫn được dùng khi gửi request render. Các cặp unchanged bị loại khỏi danh sách
action hiển thị trong tab Changes.
8. Cảnh báo trên giao diện¶
Extension hiển thị cảnh báo ở hai mức:
- Cấp file: badge
Diagram ID issuescạnh nút preview, kèm loại lỗi và số dòng. - Cấp diagram: cảnh báo trong panel khi dùng duplicate/invalid ID, migration fallback hoặc occurrence pairing có nhiều ứng viên.
Khi một stable ID ghép hai renderer khác nhau, tiêu đề panel hiển thị dạng plantuml → blockdiag và
mỗi card Before/After ghi rõ engine riêng.
9. Migration repository hiện có¶
- Tạo một MR baseline chỉ thêm
diagram-id, không đổi source, title hay engine. - Vì nhánh đích chưa có ID, MR baseline dùng legacy fallback và hiện migration warning.
- Merge baseline vào nhánh chính.
- Các branch mới phải giữ nguyên ID khi sửa source, di chuyển, đổi title hoặc đổi renderer.
Không cần tạo ID mới sau mỗi lần sửa. ID chỉ thay đổi khi diagram cũ thực sự bị thay thế bởi một diagram có danh tính khác.
10. Ghép cặp và placement là hai bước độc lập¶
MutationObserver chạy lại placement khi Rapid Diffs hoặc virtual scrolling thay đổi DOM. Việc này
chỉ di chuyển action tới đúng code block, không chạy lại hoặc thay đổi quyết định ghép cặp.
11. Khuyến nghị sử dụng¶
- Dùng một ID mô tả nghiệp vụ, ví dụ
checkout.payment-sequence. - Không nhúng vị trí hoặc số thứ tự vào ID nếu diagram có thể được di chuyển.
- Không tái sử dụng ID của diagram đã xóa cho một diagram có ý nghĩa khác.
- Khi bổ sung ID cho repository cũ, thực hiện MR baseline riêng.
- Sửa ngay duplicate/invalid warning thay vì dựa lâu dài vào legacy fallback.