JQLگزارش‌گیریJira

راهنمای عملی JQL در جیرا: کوئری‌هایی که با اضافه شدن یک وضعیت نمی‌شکنند

محمد مهیار احمدی۷ دقیقه مطالعه

هر گجت داشبورد Jira به یک Filter وصل است و هر Filter یک JQL است. اگر کوئری سست باشد، بقیهٔ زحمت داشبورد بی‌فایده است — و بدتر، کسی متوجه نمی‌شود، چون کوئری غلط هم عدد برمی‌گرداند.

statusCategory، نه فهرست کردن وضعیت‌ها

این تک‌تغییر، بیشترین اثر را روی عمر مفید گزارش‌های شما دارد. کوئری زیر رایج است و شکننده:

-- Fragile: breaks silently when a new status is added
project = OPS AND status IN ("In Progress", "In Review", "Testing")

شش ماه بعد کسی وضعیت Blocked را اضافه می‌کند و این کوئری بی‌سر‌و‌صدا آن کارها را نمی‌شمرد. نسخهٔ مقاوم:

-- Resilient: picks up any new status in the In Progress category
project = OPS
  AND statusCategory = "In Progress"
  AND resolution IS EMPTY

statusCategory فقط سه مقدار دارد — To Do، In Progress و Done — و هر وضعیتی که بسازید حتماً در یکی از آن‌ها می‌افتد. اضافه‌کردن شرط خالی‌بودن resolution هم بیمهٔ دوم است، برای وقتی که کسی دستهٔ وضعیت را اشتباه تنظیم کرده. اگر محیط شما از اول وضعیت‌های اضافه دارد، مشکل ریشه‌ای‌تر از کوئری است — طراحی Workflow در Jira همان ریشه را هدف می‌گیرد.

تلهٔ عملگر نامساوی وقتی فیلد خالی است

این یکی واقعاً آدم را غافلگیر می‌کند. در JQL، شرط نامساوی، Issueهایی را که آن فیلد در آن‌ها خالی است برنمی‌گرداند.

-- WRONG: silently excludes every unassigned issue
assignee != "ali"

-- RIGHT
assignee != "ali" OR assignee IS EMPTY

همین موضوع دربارهٔ NOT IN، fixVersion، component و هر فیلد سفارشی اختیاری هم صادق است. هر جا از نامساوی استفاده می‌کنید، از خودتان بپرسید «اگر این فیلد خالی باشد چه؟» — و اگر جوابش مهم است، شرط IS EMPTY را اضافه کنید.

زمان: created و updated و resolutiondate سه چیز متفاوت‌اند

فیلدچه زمانی را نگه می‌داردکجا به کار می‌آید
createdلحظهٔ ساخت Issueنرخ ورود کار
updatedآخرین تغییر از هر نوع، حتی یک کامنتپیدا کردن آیتم‌های رهاشده
resolutiondateلحظه‌ای که resolution ست شدهنرخ خروج و زمان چرخه
-- Closed this week -- the correct way
project = OPS AND resolutiondate >= startOfWeek()

-- Arrival rate vs. completion rate, current month
project = OPS AND created >= startOfMonth()
project = OPS AND resolutiondate >= startOfMonth()

CHANGED و WAS؛ نگاه کردن به تاریخچه

بیشتر کاربران JQL فقط وضعیت فعلی را می‌پرسند. عملگرهای تاریخچه‌ای همان جایی هستند که سؤال‌های جالب جواب می‌گیرند.

-- Bounced out of Done: a weak Definition of Done
project = OPS AND status CHANGED FROM "Done" AFTER -30d

-- Entered Review and never left
project = OPS
  AND status = "In Review"
  AND NOT status CHANGED AFTER -7d

-- Assigned once, then abandoned
project = OPS AND assignee CHANGED AND assignee IS EMPTY

-- Never entered the flow at all
project = OPS
  AND statusCategory = "To Do"
  AND created < -60d

ده کوئری که در هر محیطی می‌سازم

-- 1. Real work in progress
project = OPS AND statusCategory = "In Progress" AND resolution IS EMPTY

-- 2. Stale items, oldest first
project = OPS AND statusCategory != Done AND NOT status CHANGED AFTER -14d
ORDER BY updated ASC

-- 3. In progress but unassigned
project = OPS AND statusCategory = "In Progress" AND assignee IS EMPTY

-- 4. My open work, most urgent first
assignee = currentUser() AND statusCategory != Done ORDER BY priority DESC

-- 5. Open high-priority bugs
project = OPS AND issuetype = Bug AND priority IN (Highest, High)
  AND resolution IS EMPTY

-- 6. Epics with no open children (needs ScriptRunner)
project = OPS AND issuetype = Epic AND statusCategory != Done
  AND issueFunction NOT IN hasLinks("is Epic of")

-- 7. Completed in the current sprint
project = OPS AND sprint IN openSprints() AND statusCategory = Done

-- 8. Arrived this week
project = OPS AND created >= startOfWeek()

-- 9. Planned but not estimated
project = OPS AND sprint IN futureSprints() AND "Story Points" IS EMPTY

-- 10. Everything linked to one issue
issue IN linkedIssues("OPS-142")

Filter مالک دارد — و این یک ریسک است

وقتی کوئری را ذخیره می‌کنید، Filter به نام شما ثبت می‌شود. دو نکته که معمولاً دیر فهمیده می‌شوند:

  • اشتراک‌گذاری را با نقش پروژه انجام بدهید، نه با تک‌تک کاربرها. با نقش، عضو جدید تیم خودکار دسترسی می‌گیرد و عضو رفته خودکار از دست می‌دهد.
  • اگر مالک Filter از سازمان برود و اکانتش غیرفعال شود، داشبوردهایی که به آن وصل‌اند از کار می‌افتند. Filterهای مهم سازمانی را زیر یک اکانت سرویس یا مالکیت مشترک نگه دارید.
  • نام‌گذاری منظم داشته باشید — مثلاً OPS · WIP واقعی. یک لیست Filter با صد اسم بی‌قاعده، عملاً غیرقابل استفاده است.

کوئری سنگین، کل سیستم را کند می‌کند

کوئری‌ای که هیچ محدودیت پروژه یا زمانی ندارد، مجبور است کل ایندکس را بگردد. اگر چنین کوئری‌ای روی یک گجت داشبوردی باشد که هر بار بارگذاری صفحه اجرا می‌شود، اثرش را همهٔ کاربران حس می‌کنند.

  • همیشه با project = ... یا حداقل یک محدودیت زمانی شروع کنید.
  • از ORDER BY روی فیلدهای متنی سفارشی پرهیز کنید؛ مرتب‌سازی روی آن‌ها گران است.
  • شرط‌های تاریخچه‌ای مثل WAS و CHANGED سنگین‌ترند — بازهٔ زمانی برایشان بگذارید.

چیزهایی که با JQL نمی‌شود گرفت

خوب است مرزها را بدانید تا وقت تلف نکنید. JQL زبان انتخاب Issue است، نه زبان تحلیل. این‌ها با JQL خالی به دست نمی‌آیند:

  • میانگین زمان چرخه — JQL مجموعه برمی‌گرداند، نه محاسبهٔ آماری. سراغ گزارش‌های داخلی Board یا افزونهٔ گزارش‌گیری بروید.
  • مقایسهٔ دو بازهٔ زمانی در یک خروجی.
  • هر چیزی که به محتوای کامنت‌ها به‌شکل تحلیلی نیاز دارد.
  • شمارش تعداد دفعات ورود به یک وضعیت — می‌توانید بپرسید «آیا بوده» ولی نه «چند بار».

جمع‌بندی

سه عادت، کیفیت گزارش‌های شما را زیر و رو می‌کند: statusCategory به‌جای فهرست وضعیت‌ها، توجه به فیلدهای خالی موقع استفاده از نامساوی، و resolutiondate به‌جای updated برای هر چیزی که به «تمام شد» مربوط است. قدم بعدی این است که همین کوئری‌ها به‌جای گزارش، کار انجام دهند: قوانین Automation در Jira.

اگر می‌خواهید یک مجموعهٔ Filter و داشبورد مستند برای تیم و مدیران‌تان ساخته شود، داشبورد و گزارش مدیریتی همین کار است. و اگر ترجیح می‌دهید تیم خودتان JQL را یاد بگیرد به‌جای اینکه هر بار سفارش بدهد، دورهٔ آموزش جیرا روی نمونهٔ واقعی خودتان برگزار می‌شود.

سؤالی دربارهٔ همین موضوع دارید؟

بپرسید. اگر جوابش کوتاه باشد همان‌جا می‌گویم و اگر نیاز به بررسی داشته باشد، می‌گویم چه چیزی لازم است.

پرسیدن در واتساپ

خواندن بعدی

درخواست جلسهتماس