mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-11 19:47:12 +03:00
docs(jira): re-expand JQL depth sections and neutralize org example
Add jql-history-and-dates.md covering WAS/CHANGED predicate walkthrough,
relative-date expression tables, and saved-filter composition/naming
conventions, per foundation scrutiny depth directives. Swap cookbook
query 49's organizationMembers("Atlassian") for the YOUR_ORG placeholder
and fix the matching catalog example. All claims cited to live-verified
Atlassian docs.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent
5a097d5693
commit
3ff46ee934
@@ -254,7 +254,7 @@ SLA != completed() AND SLA <= remaining("-4h")
|
||||
|
||||
```jql
|
||||
-- 49. Requests from one customer organization (JSM triage split)
|
||||
reporter in organizationMembers("Atlassian") AND resolution = Unresolved
|
||||
reporter in organizationMembers("YOUR_ORG") AND resolution = Unresolved
|
||||
```
|
||||
|
||||
```jql
|
||||
|
||||
@@ -227,7 +227,7 @@ These require Jira Service Management and operate on the Approval and SLA custom
|
||||
|----------|--------|--------|
|
||||
| `customerDetail("Field", "Value")` | Reporter, Assignee, Voter, Watcher | `reporter in customerDetail("Region", "APAC")` |
|
||||
| `organizationDetail("Field", "Value")` | Organization | `organization in organizationDetail("Support level", "Platinum")` |
|
||||
| `organizationMembers("OrgName")` | Reporter, Assignee, Voter, Watcher | `reporter in organizationMembers("Atlassian")` |
|
||||
| `organizationMembers("OrgName")` | Reporter, Assignee, Voter, Watcher | `reporter in organizationMembers("YOUR_ORG")` |
|
||||
|
||||
`customerDetail()` and `organizationDetail()` pair with multi-select dropdown fields; chain multiple `AND` clauses for combined matches. Both return up to 32,000 records and include deleted/deactivated customers — exclude them with `AND reporter NOT IN inactiveUsers()`.
|
||||
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
# JQL History Predicates, Date Expressions & Saved Filters
|
||||
|
||||
Deep-dive on the three areas that trip up even experienced JQL writers: history operators (`WAS`, `CHANGED` and their predicates), relative-date expressions, and saved-filter composition. Pairs with [jql-functions-catalog.md](jql-functions-catalog.md), [jql-best-practices.md](jql-best-practices.md), and [jql-cookbook.md](jql-cookbook.md).
|
||||
|
||||
A JQL clause is a field followed by an operator followed by one or more values or functions (`project = "TEST"`); clauses join with keywords like `AND`/`OR`. Without parentheses a statement evaluates left-to-right, which is why parenthesizing OR groups matters.
|
||||
|
||||
## History Operators: the WAS Family
|
||||
|
||||
**Field restriction first:** `WAS`, `WAS IN`, `WAS NOT`, and `WAS NOT IN` work with **Assignee, Fix Version, Priority, Reporter, Resolution, and Status only**. On any other field they error out; custom-field history is simply not addressable through these operators.
|
||||
|
||||
### What WAS actually matches
|
||||
|
||||
`status WAS "In Progress"` finds issues that currently have OR previously had that value. Two subtle matching rules:
|
||||
|
||||
1. It matches the value name **as it was configured at the time of the change** — if your workflow renamed "In Progress" to "Active" last quarter, `status WAS "In Progress"` still finds historical states recorded under the old name.
|
||||
2. It also matches the value's numeric ID — `status WAS "Resolved"` and `status WAS "4"` hit the same issues when 4 was Resolved's ID.
|
||||
|
||||
### Optional predicates
|
||||
|
||||
Every WAS-family operator accepts optional predicates:
|
||||
|
||||
| Predicate | Form | Meaning |
|
||||
|-----------|------|---------|
|
||||
| `AFTER` | `AFTER "date"` | change happened after the date |
|
||||
| `BEFORE` | `BEFORE "date"` | change happened before the date |
|
||||
| `BY` | `BY "user"` / `BY (user1,user2)` | user who made the change |
|
||||
| `DURING` | `DURING ("date1","date2")` | change inside the window |
|
||||
| `ON` | `ON "date"` | change on that exact date |
|
||||
|
||||
The `BY` user may be a username or an Atlassian account ID (`status WAS "Resolved" BY abcde-12345-fedcba BEFORE "2019/02/02"`). Dates use the standard JQL date format (`"2019/02/02"`) or any expression from the relative-date section below — `DURING (startOfYear(), endOfYear())` is valid.
|
||||
|
||||
### Walkthrough: build a "reopened bugs" query step by step
|
||||
|
||||
Goal: bugs that went backwards from Done back to In Progress.
|
||||
|
||||
```jql
|
||||
-- Step 1: base form — did status ever hold "Done"?
|
||||
issuetype = Bug AND status WAS Done
|
||||
|
||||
-- Step 2: add the transition direction with CHANGED (below):
|
||||
issuetype = Bug AND status CHANGED FROM Done TO "In Progress"
|
||||
|
||||
-- Step 3: bound it to this year so the scan stays cheap:
|
||||
issuetype = Bug AND status CHANGED FROM Done TO "In Progress" DURING (startOfYear(), endOfYear())
|
||||
```
|
||||
|
||||
Each predicate composes: `priority CHANGED BY freddo BEFORE endOfWeek() AFTER startOfWeek()` chains two time bounds around a user bound.
|
||||
|
||||
### The other WAS operators
|
||||
|
||||
| Operator | Equivalent longhand | Example |
|
||||
|----------|--------------------|---------|
|
||||
| `WAS IN ("Resolved","Closed")` | `status WAS "Resolved" OR status WAS "Closed"` | `status WAS IN ("Resolved","In Progress")` |
|
||||
| `WAS NOT "X"` | never held X | `status WAS NOT "In Progress" BEFORE "2011/02/02"` |
|
||||
| `WAS NOT IN (...)` | `WAS NOT A AND WAS NOT B` | `status WAS NOT IN ("Resolved","In Progress")` |
|
||||
|
||||
### The 10,000-change truncation
|
||||
|
||||
If an issue has more than 10,000 changes, WAS-family queries search **only its most recent changes**. Ancient history on hyper-active issues is invisible to JQL — use the issue view or export for those. This is silent: you get results, just not complete ones.
|
||||
|
||||
## The CHANGED Operator
|
||||
|
||||
`CHANGED` finds issues whose field value *changed* (not what it changed to — that is what `FROM`/`TO` refine).
|
||||
|
||||
Predicates: everything WAS takes, **plus** `FROM "oldvalue"` and `TO "newvalue"`:
|
||||
|
||||
| Predicate | Purpose |
|
||||
|-----------|---------|
|
||||
| `FROM "oldvalue"` | previous value equals |
|
||||
| `TO "newvalue"` | new value equals |
|
||||
| `AFTER` / `BEFORE` / `DURING` / `ON` | time bounds |
|
||||
| `BY "user"` | who performed the change |
|
||||
|
||||
Same six-field restriction applies (Assignee, Fix Version, Priority, Reporter, Resolution, Status).
|
||||
|
||||
Canonical patterns:
|
||||
|
||||
```jql
|
||||
-- Any assignee change at all:
|
||||
assignee CHANGED
|
||||
|
||||
-- Regression detector: went backwards from In Progress to Open:
|
||||
status CHANGED FROM "In Progress" TO "Open"
|
||||
|
||||
-- Priority churn by one user this week:
|
||||
priority CHANGED BY freddo AFTER startOfWeek() BEFORE endOfWeek()
|
||||
|
||||
-- Resolved-by-me-this-year (cookbook #33):
|
||||
resolution CHANGED TO "Fixed" BY currentUser() DURING (startOfYear(), endOfYear())
|
||||
```
|
||||
|
||||
**Prerequisites and failure mode:** `CHANGED` and the WAS family return nothing for fields without history tracking — most system fields track, some custom fields do not. If a `CHANGED` query returns zero rows, confirm transitions actually occurred and widen the date window before assuming the data is missing. Note the docs' own quirk: the >10,000-changes truncation paragraph under CHANGED still says "the WAS operator" — same limit, shared implementation.
|
||||
|
||||
## Relative Dates and Expressions
|
||||
|
||||
### Direct offsets on date fields
|
||||
|
||||
Date fields accept an increment string directly: `(+/-)nn(y|M|w|d|h)` — years, months (capital M!), weeks, days, hours. No function call needed:
|
||||
|
||||
```jql
|
||||
created >= -7d /* last seven days */
|
||||
updated < -30d /* untouched for a month */
|
||||
duedate <= 2w /* due within two weeks */
|
||||
```
|
||||
|
||||
Case matters: `-1m` is minutes, `-1M` is months. If you drop the unit entirely the default depends on context (days for the bare-number legacy form).
|
||||
|
||||
### Function forms
|
||||
|
||||
| Expression | Evaluates to | Typical use |
|
||||
|------------|--------------|-------------|
|
||||
| `startOfDay()` | today 00:00 local | `created > startOfDay()` |
|
||||
| `endOfDay()` | today 23:59 local | `due < endOfDay("+1")` |
|
||||
| `startOfWeek()` | week start (Sunday default) | `created >= startOfWeek()` |
|
||||
| `endOfWeek()` | week end (Saturday default) | `due <= endOfWeek()` |
|
||||
| `startOfMonth()` / `endOfMonth()` | month boundaries | `resolved >= startOfMonth("-1M")` |
|
||||
| `startOfYear()` / `endOfYear()` | Jan 1 / Dec 31 | retrospective windows |
|
||||
| `now()` | exact current timestamp | `updated < now()` |
|
||||
|
||||
Offsets compose inside functions: `startOfWeek("+1d")` shifts to Monday on Sunday-default sites; `endOfMonth("+15d")` lands mid-next-month. Full parameter tables per function live in [jql-functions-catalog.md](jql-functions-catalog.md).
|
||||
|
||||
### Timezone trap
|
||||
|
||||
Jira evaluates dates in the querying user's timezone. A dashboard shared across regions shows different rows for the same `startOfDay()` query near midnight boundaries. For cross-timezone automation prefer explicit dates over day-grain relatives.
|
||||
|
||||
### Keep relatives in saved filters
|
||||
|
||||
Relative expressions re-evaluate at every run — exactly what you want in a saved filter. Freezing an absolute date into a filter meant as "this week" is a classic mistake: the filter silently stops matching next week.
|
||||
|
||||
## Saved Filters: Composition and Naming Conventions
|
||||
|
||||
Saved filters turn long JQL into reusable, shareable building blocks. From the filter lifecycle: save a search, manage/update/copy/delete it, star favorites, subscribe yourself or others to scheduled email delivery, share with colleagues (or outside the organization via links), export results (RSS, Excel), and drive dashboard gadgets.
|
||||
|
||||
### Composing queries with `filter =`
|
||||
|
||||
```jql
|
||||
filter = "My Team Open Bugs" AND priority in (High, Highest)
|
||||
filter = 10203 AND updated >= -7d -- numeric filter IDs also work
|
||||
```
|
||||
|
||||
Sub-queries compose once and get reused everywhere; fix logic in one place instead of pasting the same clause into twenty dashboards. Performance-wise this does not make Jira faster by itself (Jira expands the filter), but it makes the optimization advice in [jql-best-practices.md](jql-best-practices.md) applyable from a single edit point.
|
||||
|
||||
### Naming conventions that survive contact with reality
|
||||
|
||||
Jira does **not** enforce unique filter names — two people can each own "Open Bugs", and name-based references resolve ambiguously. Conventions that keep dashboards and subscriptions maintainable:
|
||||
|
||||
1. **Prefix by owning team or domain** — `platform-api-stale-prs`, `mobile-crash-triage`. Collisions become visible instead of silent.
|
||||
2. **Encode scope and cadence** — `weekly-security-review`, `sprint-current-blocked`. Readers of a subscription email should know cadence without opening the filter.
|
||||
3. **Never rename a filter others reference** — dashboard gadgets and subscriptions bind by filter identity, but humans navigate by name; renames strand both. Copy-and-deprecate instead.
|
||||
4. **Prefer the numeric ID in scripts** — `filter = 10203` survives renames exactly like project IDs do; reserve name-based references for interactive use.
|
||||
5. **Keep one canonical "definition" filter per recurring question** — then derive variants (`... AND assignee IS EMPTY`) rather than duplicating the whole query.
|
||||
|
||||
Sharing rules matter before composition works: a gadget or subscription breaks with "Filter not found" for any viewer lacking permission to the underlying filter — grant the audience access to the filter itself, not just the dashboard.
|
||||
|
||||
## Quick Pitfall Reference
|
||||
|
||||
| Symptom | Likely cause |
|
||||
|---------|--------------|
|
||||
| `WAS` errors on a custom field | History operators limited to Assignee/Fix Version/Priority/Reporter/Resolution/Status |
|
||||
| Old history missing on a busy issue | >10,000 changes truncated to recent-only search |
|
||||
| `CHANGED` returns nothing | No history tracking on the field, or transitions never actually happened |
|
||||
| Month offset behaved like minutes | `-1m` (minutes) vs `-1M` (months) case sensitivity |
|
||||
| Same filter shows different rows per region | Day-grain relatives evaluate in each user's timezone |
|
||||
| Gadget says "Filter not found" | Viewer lacks permission to the referenced saved filter |
|
||||
|
||||
Attribution: adapted in part from the retired jira-jql skill, sourced from Atlassian official documentation.
|
||||
|
||||
## Sources
|
||||
|
||||
- JQL operators (WAS/CHANGED/predicate reference): https://support.atlassian.com/jira-software-cloud/docs/jql-operators/
|
||||
- Advanced searching overview (clause structure, precedence, bounded JQL): https://support.atlassian.com/jira-software-cloud/docs/use-advanced-search-with-jira-query-language-jql/
|
||||
- What is advanced search (precedence, reserved words, bounded/unbounded): https://support.atlassian.com/jira-software-cloud/docs/what-is-advanced-search-in-jira-cloud/
|
||||
- JQL functions (date functions, increment syntax): https://support.atlassian.com/jira-software-cloud/docs/jql-functions/
|
||||
- Save your search as a filter: https://support.atlassian.com/jira-software-cloud/docs/save-your-search-as-a-filter/
|
||||
- JQL optimization recommendations: https://support.atlassian.com/jira-software-cloud/docs/jql-optimization-recommendations/
|
||||
- Search endpoint that runs JQL over REST (startAt/maxResults envelope): https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/
|
||||
Reference in New Issue
Block a user