diff --git a/packages/renderer/src/lib/volume/VolumesList.spec.ts b/packages/renderer/src/lib/volume/VolumesList.spec.ts index 9ad739db33cd2..57b5d5458f771 100644 --- a/packages/renderer/src/lib/volume/VolumesList.spec.ts +++ b/packages/renderer/src/lib/volume/VolumesList.spec.ts @@ -24,7 +24,7 @@ import { render, screen } from '@testing-library/svelte'; import VolumesList from './VolumesList.svelte'; import { get } from 'svelte/store'; import { providerInfos } from '/@/stores/providers'; -import { fetchVolumes, resetInitializationVolumes, volumeListInfos } from '/@/stores/volumes'; +import { volumeListInfos, volumesEventStore } from '/@/stores/volumes'; const listVolumesMock = vi.fn(); const getProviderInfosMock = vi.fn(); @@ -51,6 +51,7 @@ beforeAll(() => { beforeEach(() => { vi.resetAllMocks(); + vi.clearAllMocks(); onDidUpdateProviderStatusMock.mockImplementation(() => Promise.resolve()); getProviderInfosMock.mockResolvedValue([]); }); @@ -63,7 +64,7 @@ async function waitRender(customProperties: object): Promise { } } -test('Expect fetching in progress being displayed', async () => { +test('Expect No Container Engine being displayed', async () => { listVolumesMock.mockResolvedValue([]); getProviderInfosMock.mockResolvedValue([ { @@ -83,9 +84,7 @@ test('Expect fetching in progress being displayed', async () => { expect(noEngine).toBeInTheDocument(); }); -test('Expect no container engines being displayed', async () => { - resetInitializationVolumes(); - listVolumesMock.mockResolvedValue([]); +test('Expect volumes being displayed once extensions are started (without size data)', async () => { getProviderInfosMock.mockResolvedValue([ { name: 'podman', @@ -99,21 +98,57 @@ test('Expect no container engines being displayed', async () => { ], }, ]); + + listVolumesMock.mockResolvedValue([ + { + Volumes: [ + { + Driver: 'local', + Labels: {}, + Mountpoint: '/var/lib/containers/storage/volumes/fedora/_data', + Name: '0052074a2ade930338c00aea982a90e4243e6cf58ba920eb411c388630b8c967', + Options: {}, + Scope: 'local', + engineName: 'Podman', + engineId: 'podman.Podman Machine', + UsageData: { RefCount: 1, Size: -1 }, + containersUsage: [], + }, + ], + }, + ]); + window.dispatchEvent(new CustomEvent('extensions-already-started')); window.dispatchEvent(new CustomEvent('provider-lifecycle-change')); + // ask to fetch the volumes + const volumesEventStoreInfo = volumesEventStore.setup(); + + await volumesEventStoreInfo.fetch(); + + // first call is with listing without details + expect(listVolumesMock).toHaveBeenNthCalledWith(1, false); + // wait store are populated + while (get(volumeListInfos).length === 0) { + await new Promise(resolve => setTimeout(resolve, 500)); + } while (get(providerInfos).length === 0) { await new Promise(resolve => setTimeout(resolve, 500)); } await waitRender({}); - const noEngine = screen.getByRole('heading', { name: 'Fetching volumes...' }); - expect(noEngine).toBeInTheDocument(); + const volumeName = screen.getByRole('cell', { name: '0052074a2ade' }); + // expect size to be N/A + const volumeSize = screen.getByRole('cell', { name: 'N/A' }); + expect(volumeName).toBeInTheDocument(); + expect(volumeSize).toBeInTheDocument(); + + expect(volumeName.compareDocumentPosition(volumeSize)).toBe(4); }); -test('Expect volumes being displayed once extensions are started', async () => { +test('Expect volumes being displayed once extensions are started (with size data)', async () => { getProviderInfosMock.mockResolvedValue([ { name: 'podman', @@ -151,7 +186,12 @@ test('Expect volumes being displayed once extensions are started', async () => { window.dispatchEvent(new CustomEvent('provider-lifecycle-change')); // ask to fetch the volumes - await fetchVolumes(); + const volumesEventStoreInfo = volumesEventStore.setup(); + + await volumesEventStoreInfo.fetch('fetchUsage'); + + // first call is with listing with details + expect(listVolumesMock).toHaveBeenNthCalledWith(1, true); // wait store are populated while (get(volumeListInfos).length === 0) { diff --git a/packages/renderer/src/lib/volume/VolumesList.svelte b/packages/renderer/src/lib/volume/VolumesList.svelte index 76852f27dc59e..f60e9ee6d2a7f 100644 --- a/packages/renderer/src/lib/volume/VolumesList.svelte +++ b/packages/renderer/src/lib/volume/VolumesList.svelte @@ -4,7 +4,7 @@ import { onDestroy, onMount } from 'svelte'; import { router } from 'tinro'; import type { Unsubscriber } from 'svelte/store'; import type { VolumeInfoUI } from './VolumeInfoUI'; -import { fetchVolumes, filtered, searchPattern, volumeListInfos, volumesInitialized } from '../../stores/volumes'; +import { fetchVolumesWithData, filtered, searchPattern, volumeListInfos } from '../../stores/volumes'; import { providerInfos } from '../../stores/providers'; import NavPage from '../ui/NavPage.svelte'; import { VolumeUtils } from './volume-utils'; @@ -16,10 +16,9 @@ import StatusIcon from '../images/StatusIcon.svelte'; import Prune from '../engine/Prune.svelte'; import moment from 'moment'; import type { EngineInfoUI } from '../engine/EngineInfoUI'; -import EmptyScreen from '../ui/EmptyScreen.svelte'; import Checkbox from '../ui/Checkbox.svelte'; import Button from '../ui/Button.svelte'; -import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { faPieChart, faTrash } from '@fortawesome/free-solid-svg-icons'; let searchTerm = ''; $: searchPattern.set(searchTerm); @@ -41,24 +40,8 @@ $: selectedAllCheckboxes = volumes.filter(volume => !volume.inUse).every(volume const volumeUtils = new VolumeUtils(); -let fetchingInProgress = false; - let volumesUnsubscribe: Unsubscriber; onMount(async () => { - if (!volumesInitialized) { - fetchingInProgress = true; - try { - await fetchVolumes(); - } finally { - fetchingInProgress = false; - } - } else { - // fetch in background - fetchVolumes().catch((error: unknown) => { - console.error('unable to fetch the volumes', error); - }); - } - volumesUnsubscribe = filtered.subscribe(value => { const computedVolumes = value .map(volumeListInfo => volumeListInfo.Volumes) @@ -187,12 +170,28 @@ function computeInterval(): number { // every day return 60 * 60 * 24 * SECOND; } + +let fetchDataInProgress = false; +async function fetchUsageData() { + fetchDataInProgress = true; + try { + await fetchVolumesWithData(); + } finally { + fetchDataInProgress = false; + } +}
{#if $volumeListInfos.map(volumeInfo => volumeInfo.Volumes).flat().length > 0} + + {/if}
@@ -281,11 +280,6 @@ function computeInterval(): number { {#if providerConnections.length === 0} - {:else if fetchingInProgress} - {:else if $filtered.map(volumeInfo => volumeInfo.Volumes).flat().length === 0} {/if} diff --git a/packages/renderer/src/lib/volume/volume-utils.ts b/packages/renderer/src/lib/volume/volume-utils.ts index e1872113b01cb..8804b25264655 100644 --- a/packages/renderer/src/lib/volume/volume-utils.ts +++ b/packages/renderer/src/lib/volume/volume-utils.ts @@ -39,6 +39,10 @@ export class VolumeUtils { } getSize(volumeInfo: VolumeInfo): string { + // handle missing case + if (volumeInfo.UsageData?.Size === -1) { + return 'N/A'; + } if (volumeInfo.UsageData?.Size) { return `${filesize(volumeInfo.UsageData?.Size, { roundingMethod: 'round' })}`; } diff --git a/packages/renderer/src/stores/volumes.spec.ts b/packages/renderer/src/stores/volumes.spec.ts index ba940929e7f12..2cc038b9d5ca9 100644 --- a/packages/renderer/src/stores/volumes.spec.ts +++ b/packages/renderer/src/stores/volumes.spec.ts @@ -22,7 +22,7 @@ import { get } from 'svelte/store'; import type { Mock } from 'vitest'; import { expect, test, vi } from 'vitest'; import type { VolumeInspectInfo } from '../../../main/src/plugin/api/volume-info'; -import { fetchVolumes, volumeListInfos, volumesEventStore } from './volumes'; +import { fetchVolumesWithData, volumeListInfos, volumesEventStore } from './volumes'; // first, path window object const callbacks = new Map(); @@ -70,7 +70,7 @@ test('volumes should be updated in case of a container is removed', async () => await callback(); // now ready to fetch volumes - await fetchVolumes(); + await fetchVolumesWithData(); // now get list const volumes = get(volumeListInfos); diff --git a/packages/renderer/src/stores/volumes.ts b/packages/renderer/src/stores/volumes.ts index 6f63120efbfc4..2065b0cdee46c 100644 --- a/packages/renderer/src/stores/volumes.ts +++ b/packages/renderer/src/stores/volumes.ts @@ -52,8 +52,10 @@ export async function checkForUpdate(eventName: string): Promise { export const volumeListInfos: Writable = writable([]); // use helper here as window methods are initialized after the store in tests -const listVolumes = (): Promise => { - return window.listVolumes(); +const listVolumes = (...args: unknown[]): Promise => { + const fetchUsage = args?.length > 0 && args[0] === 'fetchUsage'; + + return window.listVolumes(fetchUsage); }; export const volumesEventStore = new EventStore( @@ -84,13 +86,6 @@ export const filtered = derived([searchPattern, volumeListInfos], ([$searchPatte }); }); -export let volumesInitialized = false; - -export const fetchVolumes = async () => { - await volumesEventStoreInfo.fetch(); - volumesInitialized = true; -}; - -export const resetInitializationVolumes = () => { - volumesInitialized = false; +export const fetchVolumesWithData = async () => { + await volumesEventStoreInfo.fetch('fetchUsage'); };