release: v1.7.28
This commit is contained in:
@@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- **Recipe scraps tips** — During cooking steps, detect "waste" generated (peels, cores, bones, eggshells, coffee grounds, citrus zest, etc.) and surface AI-powered tips on how to reuse them (compost, natural cleaner, broth, candied peel, etc.). Could be shown as an optional collapsible hint card below the step that generates the scrap.
|
- **Recipe scraps tips** — During cooking steps, detect "waste" generated (peels, cores, bones, eggshells, coffee grounds, citrus zest, etc.) and surface AI-powered tips on how to reuse them (compost, natural cleaner, broth, candied peel, etc.). Could be shown as an optional collapsible hint card below the step that generates the scrap.
|
||||||
|
|
||||||
|
## [1.7.28] - 2026-05-30
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Duplicate auto-reported issues** — The GitHub issue reporter was relying solely on the GitHub Search API for deduplication. Because search indexing has a several-minutes lag, rapid error recurrences each created a new issue before the previous one was indexed, producing ~50 duplicate issues. The reporter now uses a local file cache (`data/reported_issue_fps.json`, with `/tmp/` fallback when `data/` is not writable) as the primary deduplication store. A 30-minute per-fingerprint comment throttle is also applied to prevent flooding an existing issue. GitHub Search is used only on first run or after a cache miss. Closes [#134](https://github.com/dadaloop82/EverShelf/issues/134) (and all duplicates #135–#183).
|
||||||
|
|
||||||
## [1.7.27] - 2026-05-29
|
## [1.7.27] - 2026-05-29
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+64
-9
@@ -9931,9 +9931,41 @@ function checkUpdate(): void {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return path to the local fingerprint deduplication cache.
|
||||||
|
* Falls back to /tmp when data/ is not writable (e.g. fresh install with wrong perms).
|
||||||
|
*/
|
||||||
|
function _getFpCachePath(): string {
|
||||||
|
$primary = __DIR__ . '/../data/reported_issue_fps.json';
|
||||||
|
return is_writable(dirname($primary)) ? $primary : (sys_get_temp_dir() . '/evershelf_fps.json');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Load & prune (> 30 days) the local FP cache. */
|
||||||
|
function _loadFpCache(): array {
|
||||||
|
$path = _getFpCachePath();
|
||||||
|
if (!file_exists($path)) return [];
|
||||||
|
$data = @json_decode(@file_get_contents($path), true) ?: [];
|
||||||
|
$cutoff = time() - 30 * 86400;
|
||||||
|
return array_filter($data, fn($v) => ($v['ts'] ?? 0) > $cutoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Persist the local FP cache. */
|
||||||
|
function _saveFpCache(array $cache): void {
|
||||||
|
@file_put_contents(_getFpCachePath(), json_encode($cache), LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a GitHub issue, or add a comment to an existing open issue with the
|
* Create a GitHub issue, or add a comment to an existing open issue with the
|
||||||
* same fingerprint. Uses the REST API v3 directly (no library needed).
|
* same fingerprint. Uses the REST API v3 directly (no library needed).
|
||||||
|
*
|
||||||
|
* Deduplication strategy (two-layer):
|
||||||
|
* 1. Local file cache (data/reported_issue_fps.json or /tmp fallback) — checked
|
||||||
|
* first to avoid the GitHub Search API indexing delay that caused duplicate
|
||||||
|
* issues to be created in rapid succession.
|
||||||
|
* 2. GitHub Search API — used only on first occurrence (cache miss) as backup.
|
||||||
|
*
|
||||||
|
* Comment throttle: at most one recurrence comment per 30 minutes per fingerprint,
|
||||||
|
* to avoid flooding an issue when an error fires on every request.
|
||||||
*/
|
*/
|
||||||
function _createOrCommentGithubIssue(
|
function _createOrCommentGithubIssue(
|
||||||
string $token, string $repo,
|
string $token, string $repo,
|
||||||
@@ -9944,13 +9976,27 @@ function _createOrCommentGithubIssue(
|
|||||||
$fp = _errorFingerprint($source, $type, $message);
|
$fp = _errorFingerprint($source, $type, $message);
|
||||||
EverLog::debug('_createOrCommentGithubIssue', ['fp' => $fp, 'type' => $type]);
|
EverLog::debug('_createOrCommentGithubIssue', ['fp' => $fp, 'type' => $type]);
|
||||||
|
|
||||||
// ── 1. Search for an existing open issue with this fingerprint ─────────
|
// ── 1. Check local cache (fast, avoids Search API indexing lag) ────────
|
||||||
$searchQuery = urlencode("repo:$repo is:issue is:open label:auto-report \"fp:$fp\" in:body");
|
$fpCache = _loadFpCache();
|
||||||
$searchResult = _githubRequest($token, 'GET', "https://api.github.com/search/issues?q=$searchQuery&per_page=1");
|
|
||||||
|
|
||||||
$existingIssueNumber = null;
|
$existingIssueNumber = null;
|
||||||
if (isset($searchResult['body']['items']) && count($searchResult['body']['items']) > 0) {
|
if (isset($fpCache[$fp])) {
|
||||||
$existingIssueNumber = $searchResult['body']['items'][0]['number'] ?? null;
|
$existingIssueNumber = $fpCache[$fp]['issue'];
|
||||||
|
// Comment throttle: skip if we already commented within the last 30 min
|
||||||
|
$lastComment = $fpCache[$fp]['last_comment'] ?? 0;
|
||||||
|
if (time() - $lastComment < 1800) {
|
||||||
|
EverLog::debug('_createOrCommentGithubIssue: throttled', ['fp' => $fp]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ── 2. Fall back to GitHub Search (handles first run / cache cleared) ─
|
||||||
|
$searchQuery = urlencode("repo:$repo is:issue is:open label:auto-report \"fp:$fp\" in:body");
|
||||||
|
$searchResult = _githubRequest($token, 'GET', "https://api.github.com/search/issues?q=$searchQuery&per_page=1");
|
||||||
|
if (!empty($searchResult['body']['items'][0]['number'])) {
|
||||||
|
$existingIssueNumber = (int)$searchResult['body']['items'][0]['number'];
|
||||||
|
// Populate local cache with what we found
|
||||||
|
$fpCache[$fp] = ['issue' => $existingIssueNumber, 'ts' => time(), 'last_comment' => 0];
|
||||||
|
_saveFpCache($fpCache);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Build the common details block ─────────────────────────────────────
|
// ── Build the common details block ─────────────────────────────────────
|
||||||
@@ -9965,7 +10011,7 @@ function _createOrCommentGithubIssue(
|
|||||||
$verMd = $version ? "\n**Version:** `$version`" : '';
|
$verMd = $version ? "\n**Version:** `$version`" : '';
|
||||||
|
|
||||||
if ($existingIssueNumber) {
|
if ($existingIssueNumber) {
|
||||||
// ── 2a. Post a comment to the existing issue ──────────────────────
|
// ── 3a. Post a comment to the existing issue ──────────────────────
|
||||||
$body = "### 🔁 Recurrence — $ts\n"
|
$body = "### 🔁 Recurrence — $ts\n"
|
||||||
. "**Source:** `$source` | **Type:** `$type`\n"
|
. "**Source:** `$source` | **Type:** `$type`\n"
|
||||||
. $urlMd . $uaMd . $verMd . "\n"
|
. $urlMd . $uaMd . $verMd . "\n"
|
||||||
@@ -9975,8 +10021,11 @@ function _createOrCommentGithubIssue(
|
|||||||
"https://api.github.com/repos/$repo/issues/$existingIssueNumber/comments",
|
"https://api.github.com/repos/$repo/issues/$existingIssueNumber/comments",
|
||||||
['body' => $body]
|
['body' => $body]
|
||||||
);
|
);
|
||||||
|
// Update throttle timestamp
|
||||||
|
$fpCache[$fp]['last_comment'] = time();
|
||||||
|
_saveFpCache($fpCache);
|
||||||
} else {
|
} else {
|
||||||
// ── 2b. Create a new issue ────────────────────────────────────────
|
// ── 3b. Create a new issue ────────────────────────────────────────
|
||||||
// Determine labels from source
|
// Determine labels from source
|
||||||
$labelMap = [
|
$labelMap = [
|
||||||
'pwa' => 'js-error',
|
'pwa' => 'js-error',
|
||||||
@@ -10004,7 +10053,7 @@ function _createOrCommentGithubIssue(
|
|||||||
. "<!-- auto-report fp:$fp -->\n"
|
. "<!-- auto-report fp:$fp -->\n"
|
||||||
. "_This issue was created automatically by EverShelf's error reporter. fp:`{$fp}`_";
|
. "_This issue was created automatically by EverShelf's error reporter. fp:`{$fp}`_";
|
||||||
|
|
||||||
_githubRequest($token, 'POST',
|
$newIssueRes = _githubRequest($token, 'POST',
|
||||||
"https://api.github.com/repos/$repo/issues",
|
"https://api.github.com/repos/$repo/issues",
|
||||||
[
|
[
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
@@ -10012,6 +10061,12 @@ function _createOrCommentGithubIssue(
|
|||||||
'labels' => ['auto-report', $typeLabel],
|
'labels' => ['auto-report', $typeLabel],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
// Save to local cache immediately to prevent duplicates on rapid recurrences
|
||||||
|
$newNum = $newIssueRes['body']['number'] ?? null;
|
||||||
|
if ($newNum) {
|
||||||
|
$fpCache[$fp] = ['issue' => (int)$newNum, 'ts' => time(), 'last_comment' => time()];
|
||||||
|
_saveFpCache($fpCache);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user