Skip to content

Commit 35f15d1

Browse files
benceruleanluMyesteryactions-user
authored
feat: add job history and assets sidebar badge behavior (#9050)
## Summary Add sidebar badge behavior for queue/asset visibility updates: - Job History tab icon shows active jobs count (`queued + running`) only when the Job History panel is closed. - Assets tab icon no longer mirrors active jobs; when QPO V2 is enabled it now shows the number of assets added since the last time Assets was opened. - Opening Assets clears the unseen added-assets badge count. ## Changes - Added `iconBadge` logic to Job History sidebar tab. - Replaced Assets sidebar badge source with new unseen-assets counter logic. - Added `assetsSidebarBadgeStore` to track unseen asset additions from history updates and reset on Assets open. - Added/updated unit tests for both sidebar tab composables and the new store behavior. https://github.com/user-attachments/assets/33588a2a-c607-4fcc-8221-e7f11c3d79cc ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9050-fix-add-job-history-and-assets-sidebar-badge-behavior-30e6d73d365081c38297fe6aac9cd34c) by [Unito](https://www.unito.io) --------- Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com>
1 parent a82c984 commit 35f15d1

8 files changed

Lines changed: 386 additions & 15 deletions

src/composables/sidebarTabs/useAssetsSidebarTab.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { describe, expect, it, vi } from 'vitest'
22

33
import { useAssetsSidebarTab } from '@/composables/sidebarTabs/useAssetsSidebarTab'
44

5-
const { mockGetSetting, mockActiveJobsCount } = vi.hoisted(() => ({
5+
const { mockGetSetting, mockUnseenAddedAssetsCount } = vi.hoisted(() => ({
66
mockGetSetting: vi.fn(),
7-
mockActiveJobsCount: { value: 0 }
7+
mockUnseenAddedAssetsCount: { value: 0 }
88
}))
99

1010
vi.mock('@/platform/settings/settingStore', () => ({
@@ -17,36 +17,36 @@ vi.mock('@/components/sidebar/tabs/AssetsSidebarTab.vue', () => ({
1717
default: {}
1818
}))
1919

20-
vi.mock('@/stores/queueStore', () => ({
21-
useQueueStore: () => ({
22-
activeJobsCount: mockActiveJobsCount.value
20+
vi.mock('@/stores/workspace/assetsSidebarBadgeStore', () => ({
21+
useAssetsSidebarBadgeStore: () => ({
22+
unseenAddedAssetsCount: mockUnseenAddedAssetsCount.value
2323
})
2424
}))
2525

2626
describe('useAssetsSidebarTab', () => {
2727
it('hides icon badge when QPO V2 is disabled', () => {
2828
mockGetSetting.mockReturnValue(false)
29-
mockActiveJobsCount.value = 3
29+
mockUnseenAddedAssetsCount.value = 3
3030

3131
const sidebarTab = useAssetsSidebarTab()
3232

3333
expect(typeof sidebarTab.iconBadge).toBe('function')
3434
expect((sidebarTab.iconBadge as () => string | null)()).toBeNull()
3535
})
3636

37-
it('shows active job count when QPO V2 is enabled', () => {
37+
it('shows unseen added assets count when QPO V2 is enabled', () => {
3838
mockGetSetting.mockReturnValue(true)
39-
mockActiveJobsCount.value = 3
39+
mockUnseenAddedAssetsCount.value = 3
4040

4141
const sidebarTab = useAssetsSidebarTab()
4242

4343
expect(typeof sidebarTab.iconBadge).toBe('function')
4444
expect((sidebarTab.iconBadge as () => string | null)()).toBe('3')
4545
})
4646

47-
it('hides badge when no active jobs', () => {
47+
it('hides badge when there are no unseen added assets', () => {
4848
mockGetSetting.mockReturnValue(true)
49-
mockActiveJobsCount.value = 0
49+
mockUnseenAddedAssetsCount.value = 0
5050

5151
const sidebarTab = useAssetsSidebarTab()
5252

src/composables/sidebarTabs/useAssetsSidebarTab.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { markRaw } from 'vue'
22

33
import AssetsSidebarTab from '@/components/sidebar/tabs/AssetsSidebarTab.vue'
44
import { useSettingStore } from '@/platform/settings/settingStore'
5-
import { useQueueStore } from '@/stores/queueStore'
5+
import { useAssetsSidebarBadgeStore } from '@/stores/workspace/assetsSidebarBadgeStore'
66
import type { SidebarTabExtension } from '@/types/extensionTypes'
77

88
export const useAssetsSidebarTab = (): SidebarTabExtension => {
@@ -21,8 +21,8 @@ export const useAssetsSidebarTab = (): SidebarTabExtension => {
2121
return null
2222
}
2323

24-
const queueStore = useQueueStore()
25-
const count = queueStore.activeJobsCount
24+
const assetsSidebarBadgeStore = useAssetsSidebarBadgeStore()
25+
const count = assetsSidebarBadgeStore.unseenAddedAssetsCount
2626
return count > 0 ? count.toString() : null
2727
}
2828
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
3+
import { useJobHistorySidebarTab } from '@/composables/sidebarTabs/useJobHistorySidebarTab'
4+
5+
const { mockActiveJobsCount, mockActiveSidebarTabId } = vi.hoisted(() => ({
6+
mockActiveJobsCount: { value: 0 },
7+
mockActiveSidebarTabId: { value: null as string | null }
8+
}))
9+
10+
vi.mock('@/components/sidebar/tabs/JobHistorySidebarTab.vue', () => ({
11+
default: {}
12+
}))
13+
14+
vi.mock('@/stores/queueStore', () => ({
15+
useQueueStore: () => ({
16+
activeJobsCount: mockActiveJobsCount.value
17+
})
18+
}))
19+
20+
vi.mock('@/stores/workspace/sidebarTabStore', () => ({
21+
useSidebarTabStore: () => ({
22+
activeSidebarTabId: mockActiveSidebarTabId.value
23+
})
24+
}))
25+
26+
describe('useJobHistorySidebarTab', () => {
27+
beforeEach(() => {
28+
mockActiveSidebarTabId.value = null
29+
mockActiveJobsCount.value = 0
30+
})
31+
32+
it('shows active jobs count while the panel is closed', () => {
33+
mockActiveSidebarTabId.value = 'assets'
34+
mockActiveJobsCount.value = 3
35+
36+
const sidebarTab = useJobHistorySidebarTab()
37+
38+
expect(typeof sidebarTab.iconBadge).toBe('function')
39+
expect((sidebarTab.iconBadge as () => string | null)()).toBe('3')
40+
})
41+
42+
it('hides badge while the job history panel is open', () => {
43+
mockActiveSidebarTabId.value = 'job-history'
44+
mockActiveJobsCount.value = 3
45+
46+
const sidebarTab = useJobHistorySidebarTab()
47+
48+
expect((sidebarTab.iconBadge as () => string | null)()).toBeNull()
49+
})
50+
51+
it('hides badge when there are no active jobs', () => {
52+
mockActiveSidebarTabId.value = null
53+
mockActiveJobsCount.value = 0
54+
55+
const sidebarTab = useJobHistorySidebarTab()
56+
57+
expect((sidebarTab.iconBadge as () => string | null)()).toBeNull()
58+
})
59+
})

src/composables/sidebarTabs/useJobHistorySidebarTab.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { markRaw } from 'vue'
22

33
import JobHistorySidebarTab from '@/components/sidebar/tabs/JobHistorySidebarTab.vue'
4+
import { useQueueStore } from '@/stores/queueStore'
5+
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
46
import type { SidebarTabExtension } from '@/types/extensionTypes'
57

68
export const useJobHistorySidebarTab = (): SidebarTabExtension => {
@@ -11,6 +13,16 @@ export const useJobHistorySidebarTab = (): SidebarTabExtension => {
1113
tooltip: 'queue.jobHistory',
1214
label: 'queue.jobHistory',
1315
component: markRaw(JobHistorySidebarTab),
14-
type: 'vue'
16+
type: 'vue',
17+
iconBadge: () => {
18+
const sidebarTabStore = useSidebarTabStore()
19+
if (sidebarTabStore.activeSidebarTabId === 'job-history') {
20+
return null
21+
}
22+
23+
const queueStore = useQueueStore()
24+
const count = queueStore.activeJobsCount
25+
return count > 0 ? count.toString() : null
26+
}
1527
}
1628
}

src/stores/queueStore.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ describe('useQueueStore', () => {
535535

536536
await store.update()
537537
const initialTask = store.historyTasks[0]
538+
const initialHistoryTasks = store.historyTasks
538539

539540
// Same job with same outputs_count
540541
mockGetHistory.mockResolvedValue([{ ...job }])
@@ -543,6 +544,8 @@ describe('useQueueStore', () => {
543544

544545
// Should reuse the same instance
545546
expect(store.historyTasks[0]).toBe(initialTask)
547+
// Should preserve array identity when history is unchanged
548+
expect(store.historyTasks).toBe(initialHistoryTasks)
546549
})
547550
})
548551

src/stores/queueStore.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ export const useQueueStore = defineStore('queue', () => {
479479
const runningTasks = shallowRef<TaskItemImpl[]>([])
480480
const pendingTasks = shallowRef<TaskItemImpl[]>([])
481481
const historyTasks = shallowRef<TaskItemImpl[]>([])
482+
const hasFetchedHistorySnapshot = ref(false)
482483
const maxHistoryItems = ref(64)
483484
const isLoading = ref(false)
484485

@@ -557,7 +558,7 @@ export const useQueueStore = defineStore('queue', () => {
557558
currentHistory.map((impl) => [impl.jobId, impl])
558559
)
559560

560-
historyTasks.value = sortedHistory.map((job) => {
561+
const nextHistoryTasks = sortedHistory.map((job) => {
561562
const existing = existingByJobId.get(job.id)
562563
if (!existing) return new TaskItemImpl(job)
563564
// Recreate if outputs_count changed to ensure lazy loading works
@@ -566,6 +567,15 @@ export const useQueueStore = defineStore('queue', () => {
566567
}
567568
return existing
568569
})
570+
571+
const isHistoryUnchanged =
572+
nextHistoryTasks.length === currentHistory.length &&
573+
nextHistoryTasks.every((task, index) => task === currentHistory[index])
574+
575+
if (!isHistoryUnchanged) {
576+
historyTasks.value = nextHistoryTasks
577+
}
578+
hasFetchedHistorySnapshot.value = true
569579
} finally {
570580
// Only clear loading if this is the latest request.
571581
// A stale request completing (success or error) should not touch loading state
@@ -595,6 +605,7 @@ export const useQueueStore = defineStore('queue', () => {
595605
runningTasks,
596606
pendingTasks,
597607
historyTasks,
608+
hasFetchedHistorySnapshot,
598609
maxHistoryItems,
599610
isLoading,
600611

0 commit comments

Comments
 (0)