Filter Activity Stream

Filter Activity Stream for Jira

See what changed in any saved filter or JQL query, on a dashboard or on its own page.

Filter Activity Stream is a Forge app for Jira Cloud. It adds two places to read the stream.

It shows five kinds of events.

EventSource in JiraExample line
Createdissue created + creatorAlice Ng created the issue
Transitionedchangelog item statusBob Ray changed status from To Do to In Progress
Commentedissue comments (first 280 characters as plain text)Bob Ray commented: Keys rotated in staging
Assignedchangelog item assigneeAlice Ng assigned to Alice Ng (was Bob Ray)
Field changedevery other changelog item (priority, labels, custom fields, …)Alice Ng set labels to security

Board re-ranking ("Rank" changes) is left out. It's drag-and-drop noise.

What you can do with it

Point it at any saved filter or JQL. Pick one of your starred filters, type a filter ID or an exact filter name, or write JQL. An ORDER BY in your JQL is ignored, because the stream is always newest first.

Then narrow it down.

CSV export covers all loaded events, with UTC time, local time, issue, summary, event, actor, field, from, to and comment excerpt. The CSV opens in a dialog for you to copy into a spreadsheet, since UI Kit apps can't start a file download. Cells that start with =, +, -, @, tab or CR get a leading ', so spreadsheets don't run them as formulas. The export is limited to 2 MB.

When a limit is hit, a warning says which one. For example "Showing the newest 2,000 of 2,345 events."

Every Jira call runs as the person viewing the gadget. So restricted comments and issues they can't browse never appear, and the cache is per user.

Limits

WhatLimitWhen it is reached
Issues scanned per refresh500, most recently updated firstA warning asks you to narrow the filter or the time window
Events kept per refresh2,000, newest firstWarning shows the true total
Time windowup to 30 daysNot selectable beyond 30 days
Loading timeabout 20 s, then partial resultsA warning says "Jira was slow…"
Issues with long change historiesthe full history is read newest first for up to 100 issues per refresh, at most the newest 300 changes eachWarning names how many issues may be missing recent changes
Issues with many commentsextra comments are fetched for up to 100 issues (5 pages each)A warning says "Not every comment was loaded…"
JQL length2,000 charactersSettings error
People filter20 peopleSettings error
Cache5 minutes per user and query, up to 6.4 MB per result (2,000 events always fit unless the text is mostly control characters)"Refresh" bypasses it once the result is at least 15 s old. If a result cannot be cached, a notice says that paging and export reload it from Jira
CSV export2 MBDialog says how many rows were included

How it works

Dashboard gadget / global page (UI Kit, render: native)
  └─ invoke('getActivity' | 'exportCsv' | 'listFilters' | 'getPageConfig' | 'savePageConfig')
       └─ resolver (src/index.js → src/lib/service.js)
            ├─ Forge KVS cache  (key = sha256(accountId + query); 5-minute TTL)
            └─ api.asUser().requestJira(...)
                 1. POST /rest/api/3/search/jql
                      jql  = (<filter or JQL>) AND updated >= -<window>m ORDER BY updated DESC
                      fields = summary, created, creator, reporter, updated [, comment]
                      expand = changelog          (only when change events are wanted)
                      pages of 100 via nextPageToken, at most 500 issues
                 2. GET  /rest/api/3/issue/{id}/changelog  (only for issues whose embedded changelog is partial;
                                                            Jira returns it oldest first, so the app reads the
                                                            last page first and walks back to the window start;
                                                            up to 100 issues, 3 pages of 100 each, 4 in parallel)
                 3. GET  /rest/api/3/issue/{id}/comment?orderBy=-created   (only when the embedded comments are
                                                            partial; 4 in parallel; stops at the window start)
            → events: dedupe by id, filter by window/type/person, sort newest first, cap at 2,000

Each event has a stable id, so duplicates collapse. Changelog items use h:<historyId>:<field>:<n>, comments use c:<commentId> and creation uses n:<issueId>. The same history or comment can come from the search and from a follow-up call, and you'll see it once.

The stream is newest first. When events share a timestamp, an issue's creation is listed below the edits made with it. After that, events are ordered by type (transition, assignment, field change, comment) and then by id, so the order is the same on every refresh.

Filter names are quoted and escaped as JQL strings (" → \", \ → \\), and numeric IDs are used as-is. Before your JQL is wrapped in ( … ), the app checks it for balanced parentheses and quotes. That check follows JQL's backslash escapes inside and outside quotes, and it refuses a backslash outside quotes that isn't followed by a character. So your JQL can't close the wrapper early. It always runs with your own Jira permissions, and paths sent to Jira come from a fixed allow-list (numeric issue IDs only).

Jira errors become plain messages. Bad JQL shows Jira's own explanation, and 403 or 404 says you lack permission or the filter is missing or not shared with you. The app retries 429 and 503 twice using Retry-After (at most 3 s per wait), within the time budget. If a follow-up call for change history or comments fails, even with a timeout or network error, the stream still shows with a warning. Forge's "allow access" (user consent) error passes through untouched, so Jira can show its consent prompt.

Permissions and data

ScopeWhy
read:jira-workSearch issues, read changelogs, comments and your starred filters
read:jira-userNames and avatars of people in the stream and the people picker
storage:appThe 5-minute result cache and the global page's saved settings

The app has no external permissions, no remote back end and no egress. All code runs on Atlassian's Forge platform, so it's built to qualify for Runs on Atlassian. Atlassian decides eligibility, and you can check it with forge eligibility after deploying.

PRIVACY.md lists what is stored and for how long.

Known limitations

Support Zovo

Zovo builds and maintains this app. If it saves you time, you can support the work on GitHub Sponsors. Sponsorship is optional, and the tool works the same without it.

Zovo is made by theluckystrike on GitHub.

More tools by Zovo

License

MIT, see LICENSE.

Support this work

If this app saves you time, you can support the work on GitHub Sponsors. Monthly tiers start at $5, and one-time tiers start at $15. Sponsorship is optional, and the tool works the same without it.

Sponsor on GitHubZovo on GitHub