Skip to content

Refactor duplicate close-older search flow into a shared helper - #47870

Merged
pelikhan merged 2 commits into
mainfrom
copilot/duplicate-code-fix-older-entity-search-flow
Jul 25, 2026
Merged

Refactor duplicate close-older search flow into a shared helper#47870
pelikhan merged 2 commits into
mainfrom
copilot/duplicate-code-fix-older-entity-search-flow

Conversation

Copilot AI commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

The close-older helpers for issues, pull requests, and discussions each reimplemented the same search pipeline: build a marker query, execute search, filter results, and map matches. This change consolidates that flow so search semantics stay aligned across all three entity types.

  • Shared search pipeline

    • Added searchOlderEntitiesByMarker(...) in actions/setup/js/close_older_search_helpers.cjs
    • Centralizes:
      • marker query construction
      • empty-result handling
      • exact-marker filtering
      • summary logging
      • final result mapping
  • Entity-specific adapters kept local

    • close_older_issues.cjs
    • close_older_pull_requests.cjs
    • close_older_discussions.cjs
    • Each caller now provides only:
      • how to execute its API search
      • any entity-specific exclusions
      • how to map raw results into its return shape
  • Behavior preserved

    • Issue-specific PR exclusion remains in the issue path
    • PR-specific issue exclusion remains in the PR path
    • Discussion-specific closed/category filtering remains in the discussion path
    • Existing workflow-id / workflow-call-id / close-key behavior is unchanged
  • Focused test coverage

    • Added shared-helper tests in close_older_search_helpers.test.cjs
    • Existing issue / PR / discussion tests continue to exercise their specific adapters
return searchOlderEntitiesByMarker({
  owner,
  repo,
  workflowId,
  excludeNumber,
  entityType: "issue",
  entityQualifier: "is:issue",
  executeSearch: searchQuery =>
    github.rest.search.issuesAndPullRequests({ q: searchQuery, per_page: 50 }),
  getItems: result => result?.data?.items,
  mapItem: item => ({
    number: item.number,
    title: item.title,
    html_url: item.html_url,
  }),
});

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix duplicate code in older-entity search flow Refactor duplicate close-older search flow into a shared helper Jul 24, 2026
Copilot AI requested a review from pelikhan July 24, 2026 22:32
@github-actions

Copy link
Copy Markdown
Contributor

Triage | Category: refactor | Risk: low | Score: 33/100 (impact:12, urgency:10, quality:11)

Recommended action: batch_review (Batch A: JS refactors) — Extracts shared close-older search helper; draft, no CI yet. Low risk refactor with tests.

Score breakdown: impact=12 (DRY refactor, JS helpers), urgency=10 (draft, no CI), quality=11 (has test file)

Generated by 🔧 PR Triage Agent · sonnet46 · 31.3 AIC · ⌖ 5.51 AIC · ⊞ 5.7K ·

@pelikhan
pelikhan marked this pull request as ready for review July 25, 2026 02:31
Copilot AI review requested due to automatic review settings July 25, 2026 02:31
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has 0 new lines of code in business logic directories (threshold: 100).

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Consolidates duplicate older-entity search logic while preserving entity-specific filtering and mapping.

Changes:

  • Adds a shared marker-based search pipeline.
  • Migrates issue, pull request, and discussion adapters.
  • Adds focused shared-helper tests.
Show a summary per file
File Description
actions/setup/js/close_older_search_helpers.cjs Implements the shared search pipeline.
actions/setup/js/close_older_search_helpers.test.cjs Tests shared search behavior.
actions/setup/js/close_older_issues.cjs Adapts issue searches to the helper.
actions/setup/js/close_older_pull_requests.cjs Adapts pull request searches.
actions/setup/js/close_older_discussions.cjs Adapts discussion searches.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Medium

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Clean refactor with no blocking issues. The shared pipeline correctly centralizes marker-query construction, empty-result handling, filtering, logging, and mapping. Entity-specific callbacks preserve all existing behavior. Test coverage is adequate.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 12.9 AIC · ⌖ 4.46 AIC · ⊞ 5K

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

⚠️ Test Quality Score: 79/100 — Acceptable

Analyzed 26 test(s): 23 design, 3 implementation, 0 violation(s).

📊 Metrics (26 tests)
Metric Value
Analyzed 26 (Go: 0, JS: 26)
✅ Design 23 (88%)
⚠️ Implementation 3 (12%)
Edge/error coverage 12 (46%)
Duplicate clusters 0
Inflation No (0.30:1)
🚨 Violations 0
✅ Test Breakdown

buildMarkerSearchQuery (8 tests) — Query construction contracts:

  • Format correctness for close-older-key vs workflow-id precedence
  • Entity qualifier composition and entity-type parity
  • Security: Quote escaping to prevent query injection (2 tests)

filterByMarker (8 tests) — Filtering and exclusion contracts:

  • Single and multi-item exclusion (excludeNumber, additionalExcludeNumbers)
  • Exact marker matching vs mismatches
  • Null/undefined safety and missing body handling
  • Custom filter composition and entity-specific logic (discussions)

Parity: issues and discussions (4 tests) — Cross-entity behavioral equivalence:

  • Query and marker equivalence by entity type
  • Filter application parity (critical refactoring guarantee)

searchOlderEntitiesByMarker (3 tests) — Shared pipeline integration:

  • Input validation (no workflowId, no closeOlderKey)
  • Happy path: search → filter → map → log
  • Graceful error handling (missing items in API response)

logFilterSummary (3 tests) — Logging side effects:

  • Basic and extra-label logging formats
  • Counter handling for missing keys

Verdict

Passed. 12% implementation tests (threshold: 30%). No violations detected.

Strengths:

  • ✅ High design coverage (88%) enforces behavioral contracts across refactored entity types
  • ✅ Security-aware: quote escaping tests included
  • ✅ Dedicated parity tests ensure no regression in issue/discussion/PR flows
  • ✅ Healthy test:code ratio (0.30:1, well below 2:1 threshold)
  • ✅ Error handling for graceful failures (null items, missing body, missing API results)

Note: logFilterSummary tests verify mock calls (logging side effects), which is appropriate for testing side-effect-driven code. All assertions present; no empty test shells.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 17 AIC · ⌖ 10.3 AIC · ⊞ 7.1K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 79/100. 12% implementation tests (threshold: 30%). No violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Clean refactor — approved

This PR correctly extracts the duplicate search pipeline (query → execute → filter → log → map) into a shared searchOlderEntitiesByMarker helper. Behavior is preserved across all three entity types.

Review notes

categoryId closure capture — The categoryId parameter in searchOlderDiscussions is correctly captured by the additionalFilter arrow function closure (line 80 in the new file). No filtering regression.

Pluralization${entityType}s correctly produces issues, discussions, pull

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 · 55.6 AIC · ⌖ 4.5 AIC · ⊞ 5.7K
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /codebase-design and /tdd — no blocking issues; the consolidation is well-executed, but three gaps are worth closing before this ships.

📋 Key Themes & Highlights

Key Themes

  1. Missing test: additionalExcludeNumbers forwarding — the only caller-specific path not covered by the new searchOlderEntitiesByMarker tests. A silent regression here would cause duplicate same-run issues to leak through.
  2. Fragile pluralisation${entityType}s works today for all three callers by coincidence; adding an optional entityTypePlural param would make the API explicit and future-proof.
  3. No test for executeSearch rejection — worth pinning the propagation contract so a future try/catch refactor doesn't accidentally swallow errors.

Positive Highlights

  • ✅ The callback-based extraction (executeSearch / getItems / mapItem / additionalFilter) is a clean, flat interface — no deep inheritance or over-abstraction.
  • ✅ All entity-specific exclusions correctly remain local to each caller.
  • ✅ The three new tests cover the happy path, the early-exit (no workflowId), and the empty-result shape — solid baseline coverage.
  • ✅ Net -162 / +93 line delta is a meaningful reduction in duplicated logic.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 52.3 AIC · ⌖ 4.98 AIC · ⊞ 6.7K
Comment /matt to run again

expect(global.core.info).toHaveBeenCalledWith("No results returned from search API");
});
});
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] Missing test: additionalExcludeNumbers is not exercised inside searchOlderEntitiesByMarker tests, even though close_older_issues.cjs is the only caller that passes it. A regression that drops the forwarding in the helper would go undetected.

💡 Suggested test
it('should respect additionalExcludeNumbers from the caller', async () => {
  const executeSearch = vi.fn().mockResolvedValue({
    data: {
      items: [
        { number: 1, title: 'Old issue', body: '<!-- gh-aw-workflow-id: wf -->' },
        { number: 5, title: 'Same-run issue', body: '<!-- gh-aw-workflow-id: wf -->' },
      ],
    },
  });

  const result = await searchOlderEntitiesByMarker({
    owner: 'owner',
    repo: 'repo',
    workflowId: 'wf',
    excludeNumber: 99,
    entityType: 'issue',
    additionalExcludeNumbers: new Set([5]),
    executeSearch,
    getItems: r => r?.data?.items,
    mapItem: item => ({ number: item.number }),
  });

  expect(result).toEqual([{ number: 1 }]);
});

@copilot please address this.

const filtered = filteredItems.map(mapItem);

logFilterSummary({
entityTypePlural: `${entityType}s`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] Naive pluralisation (${entityType}s) breaks for "pull request" → logs "pull requests" correctly by coincidence, but would silently produce wrong output for any irregular plural. The existing callers pass entityType: "pull request" (with a space), yielding entityTypePlural: "pull requests" which happens to be correct — but this is fragile.

💡 Suggested fix

Add an optional entityTypePlural param (already accepted by logFilterSummary) and let callers supply it when the default is wrong:

// in searchOlderEntitiesByMarker params
entityTypePlural = `${entityType}s`,

Then pass entityTypePlural through to logFilterSummary and the info logs. Callers that already have irregular names (like "pull request") can override it.

@copilot please address this.

core.info("No results returned from search API");
return [];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] No test covers an executeSearch rejection (network error / API error). The shared helper has no try/catch, so the promise rejection propagates — but a test that asserts this behaviour would prevent future accidental swallowing of errors.

💡 Suggested test
it('should propagate executeSearch rejections', async () => {
  const err = new Error('API failure');
  await expect(
    searchOlderEntitiesByMarker({
      owner: 'owner', repo: 'repo', workflowId: 'wf',
      excludeNumber: 1, entityType: 'issue',
      executeSearch: () => Promise.reject(err),
      getItems: r => r?.data?.items,
      mapItem: item => item,
    })
  ).rejects.toThrow('API failure');
});

@copilot please address this.

@pelikhan
pelikhan merged commit d7e6e06 into main Jul 25, 2026
46 of 54 checks passed
@pelikhan
pelikhan deleted the copilot/duplicate-code-fix-older-entity-search-flow branch July 25, 2026 02:46
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[duplicate-code] Duplicate Code: Older-Entity Search Flow in Close Helpers

3 participants