Bỏ qua

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 BeforeAfter 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 {...}{...} 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:

Kroki

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:

Kroki

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.

Kroki

Vòng 1 — Ghép bằng stable ID

Một cặp được tạo khi:

  1. ID ở Before có trạng thái valid;
  2. ID ở After có trạng thái valid;
  3. 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.

Kroki

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

Kroki

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:

Kroki

Đổ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

Kroki

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

Kroki

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
...
```

Kroki

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

Kroki

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:

Kroki

comparisonSource chỉ chuẩn hóa:

  • CRLF thành LF;
  • 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 issues cạ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ó

  1. Tạo một MR baseline chỉ thêm diagram-id, không đổi source, title hay engine.
  2. Vì nhánh đích chưa có ID, MR baseline dùng legacy fallback và hiện migration warning.
  3. Merge baseline vào nhánh chính.
  4. 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

Kroki

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.