Skip to content
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

fix: fix UI not being refreshed if container is only created #5619

Merged
merged 1 commit into from Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/main/src/plugin/container-registry.ts
Expand Up @@ -129,6 +129,9 @@ export class ContainerProviderRegistry {
} else if (jsonEvent.status === 'init' && jsonEvent?.Type === 'container') {
// need to notify that a container has been started
this.apiSender.send('container-init-event', jsonEvent.id);
} else if (jsonEvent.status === 'create' && jsonEvent?.Type === 'container') {
// need to notify that a container has been created
this.apiSender.send('container-created-event', jsonEvent.id);
} else if (jsonEvent.status === 'start' && jsonEvent?.Type === 'container') {
// need to notify that a container has been started
this.apiSender.send('container-started-event', jsonEvent.id);
Expand Down
95 changes: 95 additions & 0 deletions packages/renderer/src/stores/containers.spec.ts
@@ -0,0 +1,95 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

/* eslint-disable @typescript-eslint/no-explicit-any */

import { get } from 'svelte/store';
import type { Mock } from 'vitest';
import { beforeAll, expect, test, vi } from 'vitest';
import { containersEventStore, containersInfos } from './containers';
import type { ContainerInfo } from '../../../main/src/plugin/api/container-info';

// first, path window object
const callbacks = new Map<string, any>();
const eventEmitter = {
receive: (message: string, callback: any) => {
callbacks.set(message, callback);
},
};

const listContainersMock: Mock<any, Promise<ContainerInfo[]>> = vi.fn();

Object.defineProperty(global, 'window', {
value: {
listContainers: listContainersMock,
events: {
receive: eventEmitter.receive,
},
addEventListener: eventEmitter.receive,
},
writable: true,
});

beforeAll(() => {
vi.clearAllMocks();
});

test.each([
['container-created-event'],
['container-stopped-event'],
['container-kill-event'],
['container-die-event'],
['container-init-event'],
['container-started-event'],
['container-created-event'],
['container-removed-event'],
])('fetch containers when receiving event %s', async eventName => {
// fast delays (10 & 10ms)
containersEventStore.setupWithDebounce(10, 10);

// empty list
listContainersMock.mockResolvedValue([]);

// mark as ready to receive updates
callbacks.get('extensions-already-started')();

// clear mock calls
listContainersMock.mockClear();

// now, setup at least one container
listContainersMock.mockResolvedValue([
{
Id: 'id123',
} as unknown as ContainerInfo,
]);

// send event
const callback = callbacks.get(eventName);
expect(callback).toBeDefined();
await callback();

// wait listContainersMock is called
while (listContainersMock.mock.calls.length === 0) {
await new Promise(resolve => setTimeout(resolve, 10));
}

// now get list
const containerListResult = get(containersInfos);
expect(containerListResult.length).toBe(1);
expect(containerListResult[0].Id).toEqual('id123');
});
6 changes: 4 additions & 2 deletions packages/renderer/src/stores/containers.ts
@@ -1,5 +1,5 @@
/**********************************************************************
* Copyright (C) 2022-2023 Red Hat, Inc.
* Copyright (C) 2022-2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -26,6 +26,8 @@ const windowEvents = [
'container-stopped-event',
'container-die-event',
'container-kill-event',
'container-init-event',
'container-created-event',
'container-started-event',
'container-removed-event',
'provider-change',
Expand All @@ -52,7 +54,7 @@ const listContainers = (): Promise<ContainerInfo[]> => {
return window.listContainers();
};

const containersEventStore = new EventStore<ContainerInfo[]>(
export const containersEventStore = new EventStore<ContainerInfo[]>(
'containers',
containersInfos,
checkForUpdate,
Expand Down
96 changes: 96 additions & 0 deletions packages/renderer/src/stores/pods.spec.ts
@@ -0,0 +1,96 @@
/**********************************************************************
* Copyright (C) 2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

/* eslint-disable @typescript-eslint/no-explicit-any */

import { get } from 'svelte/store';
import type { Mock } from 'vitest';
import { beforeAll, expect, test, vi } from 'vitest';
import { podsEventStore, podsInfos } from './pods';
import type { PodInfo } from '../../../main/src/plugin/api/pod-info';

// first, path window object
const callbacks = new Map<string, any>();
const eventEmitter = {
receive: (message: string, callback: any) => {
callbacks.set(message, callback);
},
};

const listPodsMock: Mock<any, Promise<PodInfo[]>> = vi.fn();

Object.defineProperty(global, 'window', {
value: {
listPods: listPodsMock,
kubernetesListPods: vi.fn().mockImplementation(() => Promise.resolve([])),
events: {
receive: eventEmitter.receive,
},
addEventListener: eventEmitter.receive,
},
writable: true,
});

beforeAll(() => {
vi.clearAllMocks();
});

test.each([
['container-created-event'],
['container-stopped-event'],
['container-kill-event'],
['container-die-event'],
['container-init-event'],
['container-started-event'],
['container-created-event'],
['container-removed-event'],
])('fetch pods when receiving event %s', async eventName => {
// fast delays (10 & 10ms)
podsEventStore.setupWithDebounce(10, 10);

// empty list
listPodsMock.mockResolvedValue([]);

// mark as ready to receive updates
callbacks.get('extensions-already-started')();

// clear mock calls
listPodsMock.mockClear();

// now, setup at least one container
listPodsMock.mockResolvedValue([
{
Id: 'id123',
} as unknown as PodInfo,
]);

// send event
const callback = callbacks.get(eventName);
expect(callback).toBeDefined();
await callback();

// wait listContainersMock is called
while (listPodsMock.mock.calls.length === 0) {
await new Promise(resolve => setTimeout(resolve, 10));
}

// now get list
const podListResult = get(podsInfos);
expect(podListResult.length).toBe(1);
expect(podListResult[0].Id).toEqual('id123');
});
7 changes: 5 additions & 2 deletions packages/renderer/src/stores/pods.ts
Expand Up @@ -29,6 +29,9 @@ const windowEvents = [
'container-stopped-event',
'container-die-event',
'container-kill-event',
'container-init-event',
'container-removed-event',
'container-created-event',
'container-started-event',
'provider-change',
'pod-event',
Expand Down Expand Up @@ -74,7 +77,7 @@ export const filtered = derived([searchPattern, podsInfos], ([$searchPattern, $i
});
});

const eventStore = new EventStore<PodInfo[]>(
export const podsEventStore = new EventStore<PodInfo[]>(
'pods',
podsInfos,
checkForUpdate,
Expand All @@ -83,7 +86,7 @@ const eventStore = new EventStore<PodInfo[]>(
grabAllPods,
PodIcon,
);
eventStore.setupWithDebounce();
podsEventStore.setupWithDebounce();

export async function grabAllPods(): Promise<PodInfo[]> {
let result = await window.listPods();
Expand Down
53 changes: 52 additions & 1 deletion packages/renderer/src/stores/volumes.spec.ts
@@ -1,5 +1,5 @@
/**********************************************************************
* Copyright (C) 2023 Red Hat, Inc.
* Copyright (C) 2023-2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -92,3 +92,54 @@ test('volumes should be updated in case of a container is removed', async () =>
const volumes2 = get(volumeListInfos);
expect(volumes2.length).toBe(0);
});

test.each([
['container-created-event'],
['container-stopped-event'],
['container-kill-event'],
['container-die-event'],
['container-init-event'],
['container-started-event'],
['container-created-event'],
['container-removed-event'],
])('fetch volumes when receiving event %s', async eventName => {
// fast delays (10 & 10ms)
volumesEventStore.setupWithDebounce(10, 10);

// empty list
listVolumesMock.mockResolvedValue([]);

// mark as ready to receive updates
callbacks.get('extensions-already-started')();

// clear mock calls
listVolumesMock.mockClear();

// now, setup listVolumesMock
listVolumesMock.mockResolvedValue([
{
Volumes: [
{
Name: 'volume1',
Driver: 'driver1',
Mountpoint: 'mountpoint1',
},
],
} as unknown as VolumeInspectInfo,
]);

// send event
const callback = callbacks.get(eventName);
expect(callback).toBeDefined();
await callback();

// wait listContainersMock is called
while (listVolumesMock.mock.calls.length === 0) {
await new Promise(resolve => setTimeout(resolve, 10));
}

// now get list
const volumeListResult = get(volumeListInfos);
expect(volumeListResult.length).toBe(1);
expect(volumeListResult[0].Volumes[0].Name).toEqual('volume1');
});
4 changes: 3 additions & 1 deletion packages/renderer/src/stores/volumes.ts
@@ -1,5 +1,5 @@
/**********************************************************************
* Copyright (C) 2022-2023 Red Hat, Inc.
* Copyright (C) 2022-2024 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -31,6 +31,8 @@ const windowEvents = [
'container-stopped-event',
'container-die-event',
'container-kill-event',
'container-init-event',
'container-created-event',
'container-started-event',
'container-removed-event',
'volume-event',
Expand Down