-
Notifications
You must be signed in to change notification settings - Fork 148
fix: 完善 Release 同步机制,修复 #119 #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
147d44f
6e2494a
b17978e
e325c09
61af71d
cfd0267
f29a425
4a23a83
39badef
b8953a0
e15747f
4caff38
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import React, { useState, useMemo, useCallback, useEffect } from 'react'; | ||
| import { Package, Bell, Search, X, RefreshCw, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, LayoutGrid, CalendarDays, ChevronDown } from 'lucide-react'; | ||
| import { Package, Bell, Search, X, RefreshCw, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, LayoutGrid, CalendarDays, ChevronDown, HelpCircle } from 'lucide-react'; | ||
| import { Release } from '../types'; | ||
| import { useAppStore } from '../store/useAppStore'; | ||
| import { GitHubApiService } from '../services/githubApi'; | ||
|
|
@@ -30,12 +30,14 @@ export const ReleaseTimeline: React.FC = () => { | |
| releaseSearchQuery, | ||
| releaseExpandedRepositories, | ||
| releaseIsRefreshing, | ||
| includePreRelease, | ||
| setReleaseViewMode, | ||
| toggleReleaseSelectedFilter, | ||
| clearReleaseSelectedFilters, | ||
| setReleaseSearchQuery, | ||
| toggleReleaseExpandedRepository, | ||
| setReleaseIsRefreshing, | ||
| setIncludePreRelease, | ||
| } = useAppStore(); | ||
|
|
||
| const { toast, confirm } = useDialog(); | ||
|
|
@@ -309,31 +311,46 @@ export const ReleaseTimeline: React.FC = () => { | |
| } | ||
|
|
||
| const allNewReleases: Release[] = []; | ||
|
|
||
| const latestReleaseTime = releases.length > 0 | ||
| ? releases.reduce((max, r) => Math.max(max, new Date(r.published_at).getTime()), 0) | ||
| : 0; | ||
| const sinceTimestamp = latestReleaseTime > 0 ? new Date(latestReleaseTime).toISOString() : undefined; | ||
| const failedRepos: { full_name: string; error: string }[] = []; | ||
|
|
||
| for (const repo of subscribedRepos) { | ||
| const [owner, name] = repo.full_name.split('/'); | ||
|
|
||
| const hasExistingReleases = releases.some(r => r.repository.id === repo.id); | ||
|
|
||
| let repoReleases: Release[]; | ||
| if (!hasExistingReleases) { | ||
| repoReleases = await githubApi.getRepositoryReleases(owner, name, 1, 10); | ||
| } else { | ||
| repoReleases = await githubApi.getIncrementalRepositoryReleases(owner, name, sinceTimestamp, 10); | ||
|
|
||
| try { | ||
| let repoReleases: Release[]; | ||
|
|
||
|
Comment on lines
+318
to
+321
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's look at the specific lines mentioned in the review comment
head -n 330 src/components/ReleaseTimeline.tsx | tail -n 20Repository: AmintaCCCP/GithubStarsManager Length of output: 722 🏁 Script executed: # Also check the type definitions to understand the Release object structure
fd -t f "\.ts$|\.tsx$" | xargs grep -l "prelease\|prerelease" | head -10Repository: AmintaCCCP/GithubStarsManager Length of output: 257 🏁 Script executed: # Check around lines 667-674 for the JSX issue mentioned
sed -n '660,680p' src/components/ReleaseTimeline.tsxRepository: AmintaCCCP/GithubStarsManager Length of output: 931 🏁 Script executed: # Check the githubApi.ts file to see the return type of getMultipleRepositoryReleases
grep -A 20 "getMultipleRepositoryReleases" src/services/githubApi.ts | head -40Repository: AmintaCCCP/GithubStarsManager Length of output: 858 🏁 Script executed: # Check type definitions for Release object
cat src/types/index.ts | grep -A 10 -B 2 "prerelease\|prelease"Repository: AmintaCCCP/GithubStarsManager Length of output: 291 🏁 Script executed: # Also check server types since releases might be defined there
cat server/src/types/api.ts | grep -A 10 -B 2 "prerelease\|prelease"Repository: AmintaCCCP/GithubStarsManager Length of output: 332 Fix Line 320 uses 🔧 Proposed fix const allNewReleases = includePreRelease?
fetchedReleases:
- fetchedReleases.filter(r => !r.prelease);
+ fetchedReleases.filter(r => !r.prerelease);🤖 Prompt for AI Agents |
||
| if (!repo.hasFetchedReleases) { | ||
| repoReleases = await githubApi.getRepositoryReleases(owner, name, 1, 30); | ||
| } else { | ||
| const since = repo.lastReleaseSyncTime; | ||
| repoReleases = await githubApi.getIncrementalRepositoryReleases( | ||
| owner, name, since, 30 | ||
| ); | ||
| } | ||
|
|
||
| if (!includePreRelease) { | ||
| repoReleases = repoReleases.filter(r => !(r as any).prerelease); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Filtering prereleases during fetch makes the toggle lossy. When 💡 Suggested directionPersist the fetched data unchanged here, and apply the toggle when deriving the visible list: const subscribedReleases = useMemo(
() =>
releases.filter(
(release) =>
releaseSubscriptions.has(release.repository.id) &&
(includePreRelease || !release.prerelease)
),
[releases, releaseSubscriptions, includePreRelease]
);If you want the toggle to change persisted data instead, it needs an explicit resync/reset path for 🤖 Prompt for AI Agents |
||
|
|
||
| repoReleases.forEach(release => { | ||
| release.repository.id = repo.id; | ||
| }); | ||
|
|
||
| allNewReleases.push(...repoReleases); | ||
|
|
||
| const updatedRepo = { | ||
| ...repo, | ||
| lastReleaseSyncTime: new Date().toISOString(), | ||
| hasFetchedReleases: true, | ||
| }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not advance the sync watermark to post-fetch wall-clock time. Setting 🕒 Safer fix+ const refreshStartedAt = new Date().toISOString();
+
for (const repo of subscribedRepos) {
// fetch...
const updatedRepo = {
...repo,
- lastReleaseSyncTime: new Date().toISOString(),
+ lastReleaseSyncTime: refreshStartedAt,
hasFetchedReleases: true,
};
updateRepository(updatedRepo);
}🤖 Prompt for AI Agents |
||
| updateRepository(updatedRepo); | ||
|
|
||
| } catch (error) { | ||
| failedRepos.push({ | ||
| full_name: repo.full_name, | ||
| error: error instanceof Error ? error.message : String(error), | ||
| }); | ||
| } | ||
|
|
||
| repoReleases.forEach(release => { | ||
| release.repository.id = repo.id; | ||
| }); | ||
|
|
||
| allNewReleases.push(...repoReleases); | ||
|
|
||
| await new Promise(resolve => setTimeout(resolve, 200)); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| const existingIds = new Set(useAppStore.getState().releases.map(r => r.id)); | ||
|
|
@@ -346,11 +363,19 @@ export const ReleaseTimeline: React.FC = () => { | |
| const now = new Date().toISOString(); | ||
| setLastRefreshTime(now); | ||
|
|
||
| const message = language === 'zh' | ||
| ? `刷新完成!发现 ${actuallyNewCount} 个新Release。` | ||
| : `Refresh completed! Found ${actuallyNewCount} new releases.`; | ||
|
|
||
| toast(message, 'success'); | ||
| // 汇总 toast | ||
| if (failedRepos.length > 0) { | ||
| const failedNames = failedRepos.map(r => r.full_name).join('、'); | ||
| const message = language === 'zh' | ||
| ? `刷新完成!新增 ${actuallyNewCount} 个Release,${failedRepos.length} 个仓库失败:${failedNames}` | ||
| : `Refresh completed! ${actuallyNewCount} new releases, ${failedRepos.length} failed: ${failedRepos.map(r => r.full_name).join(', ')}`; | ||
| toast(message, failedRepos.length > 0 ? 'warning' : 'success'); | ||
| } else { | ||
| const message = language === 'zh' | ||
| ? `刷新完成!发现 ${actuallyNewCount} 个新Release。` | ||
| : `Refresh completed! Found ${actuallyNewCount} new releases.`; | ||
| toast(message, 'success'); | ||
| } | ||
| } catch (error) { | ||
| console.error('Refresh failed:', error); | ||
| const errorMessage = language === 'zh' | ||
|
|
@@ -598,15 +623,31 @@ export const ReleaseTimeline: React.FC = () => { | |
| </span> | ||
| )} | ||
|
|
||
| {/* Refresh Button */} | ||
| <button | ||
| onClick={handleRefresh} | ||
| disabled={releaseIsRefreshing} | ||
| className="flex items-center space-x-2 px-4 py-2 bg-brand-indigo text-white rounded-lg hover:bg-brand-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed" | ||
| > | ||
| <RefreshCw className={`w-4 h-4 ${releaseIsRefreshing ? 'animate-spin' : ''}`} /> | ||
| <span>{releaseIsRefreshing ? t('刷新中...', 'Refreshing...') : t('刷新', 'Refresh')}</span> | ||
| </button> | ||
| <div className="flex items-center space-x-2"> | ||
| <button | ||
| onClick={handleRefresh} | ||
| disabled={releaseIsRefreshing} | ||
| className="flex items-center space-x-2 px-4 py-2 bg-brand-indigo text-white rounded-lg hover:bg-brand-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed" | ||
| > | ||
| <RefreshCw className={`w-4 h-4 ${releaseIsRefreshing ? 'animate-spin' : ''}`} /> | ||
| <span>{releaseIsRefreshing ? t('刷新中...', 'Refreshing...') : t('刷新', 'Refresh')}</span> | ||
| </button> | ||
| <label className="flex items-center gap-2 cursor-pointer select-none"> | ||
| <input | ||
| type="checkbox" | ||
| checked={includePreRelease} | ||
| onChange={(e) => setIncludePreRelease(e.target.checked)} | ||
| className="w-4 h-4 rounded border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-brand-indigo focus:ring-2 focus:ring-brand-violet cursor-pointer" | ||
| /> | ||
| <span className="text-sm text-gray-700 dark:text-text-secondary"> | ||
| {t('包含 Pre-release', 'Include Pre-release')} | ||
| </span> | ||
| </label> | ||
| <HelpCircle | ||
| className="w-4 h-4 text-gray-400 dark:text-text-quaternary cursor-help" | ||
| title={t('增量刷新已订阅仓库的 Release,仅获取上次同步后的新版本', 'Incremental refresh for subscribed repos (only new since last sync)')} | ||
| /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
|
|
@@ -640,12 +681,20 @@ export const ReleaseTimeline: React.FC = () => { | |
|
|
||
| {/* Filters and View Toggle Row */} | ||
| <div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3"> | ||
| <div className="flex-1"> | ||
| <div className="flex items-center gap-3 flex-wrap"> | ||
| <AssetFilterManager | ||
| selectedFilters={selectedFilters} | ||
| onFilterToggle={handleFilterToggle} | ||
| onClearFilters={handleClearFilters} | ||
| /> | ||
| <button | ||
| onClick={() => {}} | ||
| className="flex items-center gap-1 px-3 py-1.5 text-sm bg-light-surface dark:bg-white/[0.04] hover:bg-gray-200 dark:hover:bg-white/10 text-gray-700 dark:text-text-secondary rounded-lg transition-colors" | ||
| title={t('新建过滤器', 'New Filter')} | ||
| > | ||
| <Plus className="w-3.5 h-3.5" /> | ||
| <span>{t('新建', 'New')}</span> | ||
| </button> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| </div> | ||
|
|
||
| {/* View Mode Toggle Dropdown */} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.