Skip to content

fix(issues): enforce recent-first sorting across Linear, GitHub, and Jira#1290

Merged
arnestrickmann merged 3 commits intomainfrom
emdash/fix-linear-sorting-pickersearch-9vz
Mar 5, 2026
Merged

fix(issues): enforce recent-first sorting across Linear, GitHub, and Jira#1290
arnestrickmann merged 3 commits intomainfrom
emdash/fix-linear-sorting-pickersearch-9vz

Conversation

@rabanspiegel
Copy link
Contributor

@rabanspiegel rabanspiegel commented Mar 5, 2026

Summary

  • extract a shared sortByUpdatedAtDesc utility in main utils
  • apply deterministic updatedAt descending sorting for Linear, GitHub, and Jira issue list/search flows
  • keep invalid or missing timestamps at the end

Testing

  • pnpm run format
  • pnpm run lint
  • pnpm run type-check
  • pnpm exec vitest run

Context

  • fixes GEN-480 (Linear issue sorting)

Note

Low Risk
Low risk behavior change limited to client-side ordering of issue lists/search results across GitHub/Jira/Linear, with coverage for invalid/missing timestamps.

Overview
Enforces deterministic recent-first issue ordering across providers. GitHub (listIssues/searchIssues), Jira (initialFetch/searchIssues/smartSearchIssues), and Linear (initialFetch/searchIssues) now all sort results by updatedAt descending via a shared sortByUpdatedAtDesc utility, pushing missing/invalid timestamps to the end.

Adds unit tests for the new sorting utility and provider-specific tests to assert the new ordering behavior.

Written by Cursor Bugbot for commit bbe4026. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Mar 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Mar 5, 2026 2:55am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 5, 2026

Greptile Summary

This PR extracts a shared sortByUpdatedAtDesc utility in src/main/utils/issueSorting.ts and applies it consistently after every issue list/search call across the Linear, GitHub, and Jira service layers, ensuring users always see the most recently updated issues first regardless of how the backing API orders its results.

Key changes:

  • New issueSorting.ts exports HasUpdatedAt, updatedAtToTimestamp, and sortByUpdatedAtDesc — invalid/missing timestamps are normalised to 0 and sorted to the end, keeping valid entries at the top
  • LinearService: initialFetch and searchIssues both now sort client-side (the GraphQL orderBy: updatedAt is ascending by default, so the client-side sort was genuinely missing)
  • GitHubService: listIssues and searchIssues now sort the parsed CLI JSON output
  • JiraService: all five return sites (initialFetch × 2, searchIssues, smartSearchIssues × 2) wrap normalizeIssues(…) with the sort; normalizeIssues already maps fields.updatedupdatedAt, so the HasUpdatedAt contract is satisfied
  • New unit tests for the utility and integration-style tests for each service cover both the happy path and null/invalid timestamp edge cases

Minor observations:

  • JiraService.searchIssues was updated but has no corresponding test in the new JiraService.test.ts
  • Tests that assert a specific relative order among equal-timestamp items (both map to 0) implicitly rely on JavaScript's guaranteed stable sort (ECMAScript 2019+); a brief comment or looser assertion would make this assumption explicit

Confidence Score: 4/5

  • Safe to merge — logic is correct, edge cases are handled, and all changed service methods have test coverage with the small exception of JiraService.searchIssues.
  • The implementation is clean and non-breaking: sortByUpdatedAtDesc creates a new array (spread copy), handles nulls and invalid date strings gracefully, and is applied consistently. The one gap is a missing test for the JiraService.searchIssues path, and a minor implicit stable-sort assumption in two test assertions — neither affects runtime correctness.
  • src/test/main/JiraService.test.ts — missing coverage for the searchIssues method that was also updated.

Important Files Changed

Filename Overview
src/main/utils/issueSorting.ts New shared sorting utility; clean implementation with correct null/invalid handling and non-mutating sort via spread.
src/main/utils/tests/issueSorting.test.ts Good unit coverage for the utility; one test assertion for tied-timestamp ordering implicitly relies on stable sort without documentation.
src/main/services/LinearService.ts Sorts are correctly applied after the GraphQL response is received in both initialFetch and searchIssues.
src/main/services/GitHubService.ts Sort applied after JSON.parse on CLI output for both listIssues and searchIssues; updatedAt is already included in the --json field list.
src/main/services/JiraService.ts Sort consistently applied after every normalizeIssues call; normalizeIssues maps fields.updatedupdatedAt which is compatible with HasUpdatedAt.
src/test/main/JiraService.test.ts New tests for initialFetch and smartSearchIssues sorting; searchIssues path is not covered despite also receiving the sort change.
src/test/main/LinearService.test.ts Covers both initialFetch and searchIssues sorting, including the null/invalid timestamp edge cases.
src/test/main/GitHubService.test.ts Adds mock routing for gh issue list commands and two new integration-style tests verifying descending sort for list and search flows.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    GH_LIST["GitHubService.listIssues()"] --> GH_PARSE["JSON.parse(gh issue list output)"]
    GH_SEARCH["GitHubService.searchIssues()"] --> GH_PARSE2["JSON.parse(gh issue list --search output)"]
    LIN_FETCH["LinearService.initialFetch()"] --> LIN_GQL["GraphQL: issues(orderBy: updatedAt)"]
    LIN_SEARCH["LinearService.searchIssues()"] --> LIN_GQL2["GraphQL: searchIssues(term)"]
    JIRA_INIT["JiraService.initialFetch()"] --> JIRA_RAW["searchRaw (JQL ORDER BY updated DESC)"]
    JIRA_SI["JiraService.searchIssues()"] --> JIRA_RAW2["searchRaw (text ~ term)"]
    JIRA_SMART["JiraService.smartSearchIssues()"] --> JIRA_KEY{"Direct key\nlookup?"}
    JIRA_KEY -- yes --> JIRA_GET["getIssueByKey()"]
    JIRA_KEY -- no --> JIRA_JQL["searchRaw (JQL)"]

    GH_PARSE --> SORT["sortByUpdatedAtDesc()"]
    GH_PARSE2 --> SORT
    LIN_GQL --> SORT
    LIN_GQL2 --> SORT
    JIRA_RAW --> NORM["normalizeIssues()\n(fields.updated → updatedAt)"]
    JIRA_RAW2 --> NORM
    JIRA_GET --> NORM
    JIRA_JQL --> NORM
    NORM --> SORT

    SORT --> OUT["Caller receives issues\nsorted most-recent-first"]

    subgraph issueSorting.ts
        SORT
        TS["updatedAtToTimestamp()\nnull / invalid → 0"]
        SORT --> TS
    end
Loading

Last reviewed commit: f0bfd80

Comment on lines +28 to +37
it('pushes missing or invalid updatedAt to the end', () => {
const issues = [
{ id: 'missing', updatedAt: null },
{ id: 'valid', updatedAt: '2026-03-04T00:00:00.000Z' },
{ id: 'invalid', updatedAt: 'bogus' },
];

const sorted = sortByUpdatedAtDesc(issues);
expect(sorted.map((issue) => issue.id)).toEqual(['valid', 'missing', 'invalid']);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implicit stable-sort dependency in test assertion

The expected output ['valid', 'missing', 'invalid'] relies on a stable sort to preserve the relative input order of 'missing' (index 0) and 'invalid' (index 2) since both map to timestamp 0. JavaScript's Array.prototype.sort has been required to be stable since ECMAScript 2019, so this will work on all modern runtimes, but the test gives no indication that this assumption is intentional.

Consider either documenting the assumption with a comment, or explicitly testing only that the two invalid entries appear after the valid one (e.g. checking sorted[0].id === 'valid' and that the set of the remaining two items equals new Set(['missing', 'invalid'])). This is especially relevant because the same pattern appears in LinearService.test.ts (lines 56–63), where GEN-200 (null) and GEN-201 ('not-a-date') are also expected to be in a specific relative order despite being tied at timestamp 0.

Comment on lines +1 to +5
import { beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest';

vi.mock('electron', () => ({
app: { getPath: () => '/tmp/test-emdash' },
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for searchIssues

The new JiraService.test.ts covers initialFetch and smartSearchIssues, but JiraService.searchIssues (a separate public method that also received the sortByUpdatedAtDesc wrapping in this PR at line 164 of JiraService.ts) has no test. For full coverage of the new sorting behaviour it's worth adding a test for that path as well.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@arnestrickmann arnestrickmann merged commit 14240ce into main Mar 5, 2026
5 checks passed
@rabanspiegel rabanspiegel deleted the emdash/fix-linear-sorting-pickersearch-9vz branch March 5, 2026 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants