Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 38 — JQL Functions Advanced

JIRA and Confluence for BA Bài 38/60

Mở đầu — vì sao bài này quan trọng

Ở các bài JQL trước, bạn đã biết cách viết những truy vấn cơ bản như project = "MOBILE" AND status = "In Progress". Những câu lệnh đó rất hữu ích, nhưng chúng có một điểm yếu cố hữu: chúng "cứng". Khi bạn viết due <= "2026-06-30", ngày mai bạn vẫn phải sửa lại con số đó bằng tay. Filter bạn lưu hôm nay sẽ sai vào tuần sau. Dashboard của bạn sẽ hiển thị dữ liệu lỗi thời mà bạn không hề hay biết.

Đây chính là lúc các JQL functions (hàm JQL) phát huy sức mạnh. Một function là một biểu thức động — JIRA sẽ tính toán lại giá trị của nó mỗi lần bạn chạy truy vấn. Khi bạn viết due <= endOfWeek(), JIRA tự hiểu "cuối tuần này là khi nào" tại đúng thời điểm bạn nhấn Search. Filter trở thành "sống", dashboard luôn cập nhật, và bạn không bao giờ phải sửa ngày tháng thủ công nữa.

Với một BA, đây là kỹ năng tạo ra sự khác biệt giữa người "biết dùng JIRA" và người "khai thác JIRA như một công cụ phân tích thực thụ". Bạn sẽ xây được những bộ filter tự động báo cáo việc quá hạn, những bảng theo dõi sprint tự làm mới, và những truy vấn thông minh phản ánh đúng vai trò, cấu trúc của tổ chức. Bài này tập trung sâu vào ba nhóm function quan trọng nhất: hàm thời gian (time-based), hàm liên quan đến người dùng và thành viên, và hàm dựa trên cấu trúc dữ liệu (membership/list functions).

Khái niệm cốt lõi

Function là gì trong JQL

Trong JQL, một function trông giống một lời gọi hàm trong lập trình: có tên hàm, theo sau là cặp ngoặc đơn (), và đôi khi có tham số bên trong ngoặc. Ví dụ now(), currentUser(), startOfDay(), membersOf("ba-team"). Điểm mấu chốt cần nhớ: function luôn nằm ở phía bên phải của toán tử so sánh, đóng vai trò là một giá trị động.

Cú pháp tổng quát:

<field> <operator> <function>()

Ví dụ: assignee = currentUser() nghĩa là "những issue được gán cho chính tôi (người đang đăng nhập)".

Nhóm hàm thời gian (Time-based functions)

Đây là nhóm hàm bạn sẽ dùng nhiều nhất. Chúng trả về một mốc thời gian được tính tương đối so với thời điểm hiện tại.

  • now() — thời điểm hiện tại, chính xác đến từng phút. Dùng để so sánh "đã quá hạn hay chưa", "đã qua hay sắp tới".
  • startOfDay() / endOfDay() — đầu ngày (00:00) và cuối ngày (23:59) của hôm nay.
  • startOfWeek() / endOfWeek() — đầu tuần và cuối tuần hiện tại. Lưu ý: ngày bắt đầu tuần phụ thuộc vào thiết lập ngôn ngữ (locale) của tài khoản — ở Việt Nam thường là thứ Hai.
  • startOfMonth() / endOfMonth() — đầu tháng và cuối tháng.
  • lastLogin() — thời điểm đăng nhập trước đó của bạn (hữu ích để xem "có gì thay đổi từ lần trước tôi vào").
Sức mạnh thực sự nằm ở tham số dịch chuyển (offset). Mỗi hàm thời gian nhận một tham số dạng chuỗi để cộng/trừ thời gian:

  • startOfDay("-7d") — đầu ngày của 7 ngày trước.
  • endOfWeek("+1w") — cuối tuần của tuần sau.
  • startOfMonth("-1") — đầu tháng trước (số không kèm đơn vị mặc định theo đơn vị của hàm).
  • now("-2h") — 2 giờ trước.
Các đơn vị hỗ trợ: m (phút), h (giờ), d (ngày), w (tuần). Riêng startOfMonth/endOfMonth còn hiểu M (tháng) và y (năm), ví dụ startOfMonth("-3M") là đầu tháng của 3 tháng trước.

Một mẹo quan trọng: để lọc theo một khoảng thời gian, bạn kết hợp hai mốc bằng AND:

created >= startOfWeek() AND created <= endOfWeek()

Nhóm hàm người dùng và thành viên

  • currentUser() — người đang đăng nhập. Đây là chìa khóa để tạo ra một filter dùng chung cho cả team mà mỗi người vào đều thấy việc của riêng mình.
  • membersOf("group-name") — trả về toàn bộ thành viên của một group. Dùng với toán tử in: assignee in membersOf("ba-team").
  • currentLogin() — thời điểm bắt đầu phiên đăng nhập hiện tại.

Nhóm hàm liên quan tới issue và history

  • linkedIssues("KEY") — các issue liên kết với issue có key chỉ định.
  • issueHistory() — các issue bạn đã xem gần đây.
  • votedIssues()watchedIssues() — issue bạn đã vote hoặc đang theo dõi.
  • updatedBy("username") — issue được cập nhật bởi một người cụ thể (có thể kèm khoảng thời gian).
  • Với JIRA dùng Sprint: openSprints(), closedSprints(), futureSprints() giúp lọc issue theo trạng thái sprint mà không cần gõ tên sprint cụ thể.

Toán tử WAS, CHANGED kết hợp function

Một điểm nâng cao ít người biết: function thời gian kết hợp được với toán tử lịch sử như WAS, CHANGED, DURING. Ví dụ status CHANGED TO "Done" DURING (startOfWeek(), endOfWeek()) cho bạn tất cả issue được chuyển sang Done trong tuần này — cực kỳ hữu ích cho báo cáo thông lượng (throughput).

Tình huống thực tế

Tình huống 1 — Tiki và bài toán "việc quá hạn của riêng tôi"

Tại một nhóm phát triển sản phẩm của một sàn thương mại điện tử lớn như Tiki, mỗi BA phụ trách 30–40 user story cùng lúc trong một quý. Chị Lan, BA trưởng nhóm Logistics, liên tục phàn nàn rằng mỗi sáng chị phải lọc tay để biết story nào của mình đã quá hạn.

Giải pháp là một filter duy nhất, lưu lại và ghim lên đầu board:

assignee = currentUser()
AND resolution = Unresolved
AND due < now()
ORDER BY due ASC

Điều tinh tế ở đây: vì dùng currentUser(), filter này dùng chung được cho cả 5 BA trong nhóm. Mỗi người mở ra chỉ thấy việc quá hạn của chính mình. Và vì dùng now(), danh sách tự cập nhật mỗi sáng — không ai phải sửa ngày. Sau hai tuần áp dụng, tỷ lệ story bị bỏ quên quá hạn của nhóm chị Lan giảm từ khoảng 18% xuống dưới 5%.

Bài học rút ra: currentUser() biến một filter cá nhân thành tài sản dùng chung của cả team; now() khiến nó tự sống mà không cần bảo trì.

Tình huống 2 — Công ty fintech MoMo và báo cáo sprint hàng tuần

Một nhóm tại công ty ví điện tử MoMo chạy sprint hai tuần. Mỗi sáng thứ Hai, Product Owner cần một báo cáo "tuần vừa rồi đội đã hoàn thành bao nhiêu story và bao nhiêu story mới được tạo". Trước đây người ta xuất Excel thủ công, mất gần một tiếng.

BA của nhóm dựng hai filter động:

Số story hoàn thành trong tuần trước:

project = WALLET
AND issuetype = Story
AND status CHANGED TO "Done"
DURING (startOfWeek("-1w"), endOfWeek("-1w"))

Số story mới tạo trong tuần trước:

project = WALLET
AND issuetype = Story
AND created >= startOfWeek("-1w")
AND created <= endOfWeek("-1w")

Hai filter này được nhúng vào một dashboard. Mỗi sáng thứ Hai chỉ cần mở dashboard là có ngay con số, không thao tác gì thêm. Vì dùng offset "-1w", hệ thống luôn tự hiểu "tuần trước" là tuần nào, kể cả khi đầu năm hay cuối tháng.

Bài học rút ra: kết hợp startOfWeek/endOfWeek với offset và toán tử DURING cho phép tạo báo cáo định kỳ tự động hoàn toàn — đây là nền tảng cho mọi dashboard "sống".

Tình huống 3 — Ngân hàng số tại Singapore và việc phân quyền theo nhóm

Một ngân hàng số ở Singapore (giả định tên DBS Digital) có quy định kiểm soát nội bộ: mọi yêu cầu thay đổi nghiệp vụ (change request) đang chờ duyệt mà được giao cho thành viên ngoài nhóm BA chính thức đều bị coi là rủi ro tuân thủ. Đội kiểm soát cần một truy vấn để rà soát hằng ngày.

Họ tạo group ba-core gồm các BA được ủy quyền, rồi viết:

project = "CR"
AND status = "Pending Approval"
AND assignee NOT IN membersOf("ba-core")

Truy vấn này lập tức phơi bày mọi change request đang "lạc" sang người ngoài nhóm. Kết hợp thêm yếu tố thời gian để bắt những case tồn đọng lâu:

project = "CR"
AND status = "Pending Approval"
AND assignee NOT IN membersOf("ba-core")
AND updated < startOfDay("-2d")

Phần updated < startOfDay("-2d") lọc ra những issue đã không được động đến trong hơn 2 ngày — vừa sai người, vừa bị bỏ quên.

Bài học rút ra: membersOf() cho phép viết truy vấn theo vai trò tổ chức thay vì theo tên từng người. Khi nhân sự thay đổi, bạn chỉ cần cập nhật group, mọi filter tự đúng theo — cực kỳ quan trọng trong môi trường có yêu cầu tuân thủ.

Hướng dẫn từng bước

Hãy cùng xây một truy vấn nâng cao từ đầu để bạn nắm quy trình tư duy.

Bước 1 — Phát biểu nhu cầu bằng tiếng Việt trước. Ví dụ: "Tôi muốn xem các bug được giao cho thành viên nhóm QA, được tạo trong tháng này, và vẫn chưa giải quyết, sắp xếp theo độ ưu tiên giảm dần."

Bước 2 — Dịch từng mệnh đề sang field tương ứng.

  • "bug" → issuetype = Bug
  • "giao cho thành viên nhóm QA" → assignee in membersOf("qa-team")
  • "tạo trong tháng này" → created >= startOfMonth()
  • "chưa giải quyết" → resolution = Unresolved
  • "ưu tiên giảm dần" → ORDER BY priority DESC
Bước 3 — Ghép lại bằng AND.

issuetype = Bug
AND assignee in membersOf("qa-team")
AND created >= startOfMonth()
AND resolution = Unresolved
ORDER BY priority DESC

Bước 4 — Dùng chế độ gợi ý của JIRA. Khi gõ trong ô JQL ở chế độ Advanced, JIRA tự động gợi ý tên function và tham số. Hãy tận dụng autocomplete để tránh gõ sai tên group hay tên field.

Bước 5 — Chạy thử và kiểm tra biên. Đổi startOfMonth() thành startOfMonth("-1M") để xem dữ liệu tháng trước, đối chiếu xem con số có hợp lý không. Đây là cách kiểm tra nhanh xem function offset có hoạt động đúng như bạn nghĩ.

Bước 6 — Lưu thành filter. Đặt tên rõ ràng như "Bug QA chưa xử lý — tháng hiện tại", rồi chia sẻ cho nhóm hoặc nhúng vào dashboard. Vì truy vấn dùng function động, filter này sẽ tự đúng mỗi tháng.

Lỗi thường gặp & mẹo

Lỗi 1 — Đặt function sai vị trí. Function phải nằm bên phải toán tử. currentUser() = assignee sẽ báo lỗi; phải viết assignee = currentUser().

Lỗi 2 — Dùng toán tử sai với function trả về danh sách. membersOf() trả về nhiều người, nên phải dùng in chứ không phải =. Viết assignee = membersOf("ba-team") sẽ lỗi; phải là assignee in membersOf("ba-team").

Lỗi 3 — Nhầm cú pháp offset. Tham số offset phải là chuỗi trong dấu nháy: startOfDay("-7d"). Viết startOfDay(-7d) không có nháy sẽ báo lỗi cú pháp. Ngoài ra đừng quên dấu của offset — "7d""-7d" cho kết quả ngược nhau (tương lai so với quá khứ).

Lỗi 4 — Quên rằng tuần bắt đầu theo locale. startOfWeek() của một tài khoản đặt locale tiếng Anh (Mỹ) bắt đầu từ Chủ Nhật, trong khi tài khoản locale Việt Nam thường bắt đầu từ thứ Hai. Nếu báo cáo của bạn lệch một ngày, hãy kiểm tra thiết lập ngôn ngữ tài khoản trước khi nghi ngờ truy vấn.

Lỗi 5 — Lẫn lộn due, created, updated, resolved. Function thời gian không tự biết bạn muốn so với field nào. Một truy vấn "việc quá hạn" phải dùng due < now(), còn "việc mới tạo hôm nay" là created >= startOfDay(). Chọn sai field là nguyên nhân số một khiến báo cáo cho ra số liệu vô lý.

Mẹo 1 — Dùng now() thay vì gõ ngày cứng cho mọi điều kiện "quá hạn/sắp tới". Đây là thói quen giúp filter của bạn không bao giờ lỗi thời.

Mẹo 2 — Kết hợp resolution = Unresolved với due < now(). Nếu chỉ viết due < now(), bạn sẽ thấy cả những việc đã xong nhưng từng quá hạn. Thêm điều kiện chưa resolve để chỉ lấy việc thực sự cần xử lý.

Mẹo 3 — Tận dụng openSprints() cho board động. Thay vì gõ tên sprint (sẽ đổi mỗi hai tuần), sprint in openSprints() luôn trỏ đúng sprint đang chạy.

Mẹo 4 — Test offset bằng cách so sánh thủ công. Khi không chắc startOfMonth("-1M") cho ra ngày nào, hãy thêm ORDER BY created ASC và nhìn issue đầu danh sách để xác nhận mốc thời gian.

Bài tập thực hành

Hãy thử viết các truy vấn sau trong JIRA của bạn (hoặc trên môi trường sandbox). Cố gắng tự viết trước khi đối chiếu gợi ý.

  • Liệt kê tất cả issue được gán cho chính bạn, chưa giải quyết, và có hạn trong 7 ngày tới.
- Gợi ý: assignee = currentUser() AND resolution = Unresolved AND due <= endOfDay("+7d")

  • Tìm tất cả Story được tạo trong tháng trước thuộc dự án của bạn, sắp xếp theo ngày tạo mới nhất.
- Gợi ý: dùng created >= startOfMonth("-1M") AND created <= endOfMonth("-1M").

  • Tìm các bug được cập nhật bởi thành viên nhóm developer trong tuần này.
- Gợi ý kết hợp membersOf() với updatedBy() và khoảng startOfWeek()endOfWeek().

  • Tạo một filter báo cáo: tất cả issue chuyển sang trạng thái Done trong hôm nay.
- Gợi ý: status CHANGED TO Done DURING (startOfDay(), endOfDay()).

  • (Nâng cao) Tìm các change request đang Pending Approval, giao cho người ngoài nhóm ba-core, và không được động tới hơn 3 ngày. Viết lại tình huống 3 với offset "-3d".
Sau khi viết xong, hãy lưu ít nhất hai truy vấn thành filter và đặt tên có ý nghĩa. Mục tiêu là bạn rời bài học này với một bộ filter động thực sự dùng được trong công việc hằng ngày.

Tóm tắt

JQL functions là thứ biến những truy vấn tĩnh thành công cụ phân tích sống. Ba nhóm function quan trọng nhất với một BA là: hàm thời gian (now(), startOfDay/Week/Month(), endOf...() kèm offset như "-7d", "-1M") giúp tạo báo cáo tự cập nhật; hàm người dùng (currentUser(), membersOf()) giúp viết filter theo vai trò và dùng chung cho cả team; và các hàm lịch sử kết hợp DURING, CHANGED giúp đo lường thông lượng theo thời gian.

Hãy ghi nhớ bốn nguyên tắc: function luôn nằm bên phải toán tử; dùng in với function trả về danh sách; offset phải là chuỗi có dấu nháy và đúng dấu; và luôn chọn đúng field thời gian (due cho hạn, created cho tạo mới, updated cho cập nhật). Khi đã thành thạo, bạn sẽ không còn phải sửa ngày tháng thủ công nữa — filter và dashboard của bạn tự đúng mỗi ngày, mỗi tuần, mỗi tháng. Đó chính là dấu hiệu của một BA làm chủ JIRA thực thụ.