mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-11-10 12:28:27 +00:00
feat(activity): Clear Completed also clears finished requests with no download (#1650)
A request that auto-download closes because the book is already in the library never gets a download, so Clear Completed skipped it and those requests piled up under Complete. The button also disappeared once the last finished download was cleared, leaving no way to clear them. ActivitySidebar.tsx: hasTerminalDownloadItems is dropped; the button shows whenever getClearCompletedTargets returns anything, and on the Requests tab as well as Downloads and All. clearCompleted.ts: a request card with no download of its own is a target when its request is fulfilled with no active delivery_state (queued, resolving, locating, downloading), or cancelled. Pending and rejected requests are left alone. The server already treats any fulfilled request as terminal in _request_terminal_status, so the client rule is the stricter of the two. docs/users-and-requests.md: a line under Clearing the activity list saying what the button clears. Tests: two new cases in clearCompleted.test.ts, one for a closed request and a cancelled one with no download, one that pending, rejected and still-downloading requests are not touched. Builds on #1438. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **New Features** * **Clear Completed** can appear on the Requests tab, as well as Downloads and All, when there are items to clear. * Clearing completed items removes finished downloads and their requests, fulfilled requests that are no longer in progress, and cancelled requests without downloads. Pending and rejected requests remain. * **Documentation** * Updated the help documentation to explain what **Clear Completed** removes. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
371ac75954
commit
c38f4c6522
4 files changed
+57
-13
No files matched your search
@@ -69,6 +69,10 @@ Admins can override the default ebook/audiobook modes and request rules for indi
|
||||
|
||||
---
|
||||
|
||||
## Clearing the activity list
|
||||
|
||||
**Clear Completed** in the activity list clears finished downloads and their requests, and also finished requests that never had a download, such as a request closed because the book was already in the library. Pending and rejected requests are left alone.
|
||||
|
||||
## Requests
|
||||
|
||||
The request system controls whether users can download directly or need admin approval first.
|
||||
|
||||
@@ -379,17 +379,6 @@ export const ActivitySidebar = ({
|
||||
};
|
||||
}, [downloadItems, visibleRequestItems]);
|
||||
|
||||
const hasTerminalDownloadItems = useMemo(
|
||||
() =>
|
||||
mergedDownloadItems.some(
|
||||
(item) =>
|
||||
item.visualStatus === 'complete' ||
|
||||
item.visualStatus === 'error' ||
|
||||
item.visualStatus === 'cancelled',
|
||||
),
|
||||
[mergedDownloadItems],
|
||||
);
|
||||
|
||||
const allItems = useMemo(() => {
|
||||
const combined = dedupeById([...mergedDownloadItems, ...mergedRequestItems]);
|
||||
return combined.toSorted((a, b) => b.timestamp - a.timestamp);
|
||||
@@ -958,8 +947,9 @@ export const ActivitySidebar = ({
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{(effectiveActiveTab === 'downloads' || effectiveActiveTab === 'all') &&
|
||||
hasTerminalDownloadItems &&
|
||||
{(effectiveActiveTab === 'downloads' ||
|
||||
effectiveActiveTab === 'all' ||
|
||||
effectiveActiveTab === 'requests') &&
|
||||
clearCompletedTargets.length > 0 && (
|
||||
<div
|
||||
className="flex items-center justify-center border-t p-3"
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import type { ActivityDismissTarget } from './ActivitySidebar';
|
||||
import type { ActivityItem } from './activityTypes';
|
||||
|
||||
// A fulfilled request whose download is still under way is not finished.
|
||||
const ACTIVE_DELIVERY_STATES = new Set(['queued', 'resolving', 'locating', 'downloading']);
|
||||
|
||||
/**
|
||||
* Items the "Clear Completed" button dismisses: every finished download, plus the request
|
||||
* linked to it once that request is itself finished. The server only dismisses finished
|
||||
* requests and rejects the whole batch if one is still pending, which a restart can cause
|
||||
* by reopening the request of a download that already ended.
|
||||
*
|
||||
* Also every finished request card that has no download of its own: an approved request
|
||||
* nothing is still downloading for (auto-download closes a request whose book is already
|
||||
* in the library without downloading anything), or a cancelled one. Rejected requests
|
||||
* stay; they have their own view.
|
||||
*/
|
||||
export const getClearCompletedTargets = (
|
||||
visibleItems: ActivityItem[],
|
||||
@@ -21,6 +29,20 @@ export const getClearCompletedTargets = (
|
||||
const seen = new Set<string>();
|
||||
|
||||
visibleItems.forEach((item) => {
|
||||
if (item.kind === 'request' && typeof item.requestId === 'number') {
|
||||
const record = item.requestRecord;
|
||||
const finished =
|
||||
record?.status === 'cancelled' ||
|
||||
(record?.status === 'fulfilled' &&
|
||||
!ACTIVE_DELIVERY_STATES.has(record.delivery_state ?? ''));
|
||||
const requestKey = `request:${item.requestId}`;
|
||||
if (finished && !pendingRequestIds.has(item.requestId) && !seen.has(requestKey)) {
|
||||
seen.add(requestKey);
|
||||
targets.push({ itemType: 'request', itemKey: requestKey });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const isTerminalDownload =
|
||||
item.kind === 'download' &&
|
||||
(item.visualStatus === 'complete' ||
|
||||
|
||||
@@ -98,4 +98,32 @@ describe('getClearCompletedTargets', () => {
|
||||
|
||||
expect(targets).toEqual([]);
|
||||
});
|
||||
|
||||
it('clears a finished request that never had a download', () => {
|
||||
const closed = makeRequestItem(30, 'fulfilled');
|
||||
closed.requestRecord = Object.assign({}, closed.requestRecord, {
|
||||
delivery_state: 'complete' as const,
|
||||
admin_note: '[auto] Already in the library.',
|
||||
});
|
||||
const cancelled = makeRequestItem(31, 'cancelled');
|
||||
|
||||
const targets = getClearCompletedTargets([closed, cancelled], [closed, cancelled]);
|
||||
|
||||
expect(targets).toEqual([
|
||||
{ itemType: 'request', itemKey: 'request:30' },
|
||||
{ itemType: 'request', itemKey: 'request:31' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('leaves pending, rejected and still-downloading requests alone', () => {
|
||||
const pending = makeRequestItem(40, 'pending');
|
||||
const rejected = makeRequestItem(41, 'rejected');
|
||||
const downloading = makeRequestItem(42, 'fulfilled');
|
||||
downloading.requestRecord = Object.assign({}, downloading.requestRecord, {
|
||||
delivery_state: 'downloading' as const,
|
||||
});
|
||||
|
||||
const items = [pending, rejected, downloading];
|
||||
expect(getClearCompletedTargets(items, items)).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user