From 4696e3f82c556fd6d269d75573f2dcfead407b8d Mon Sep 17 00:00:00 2001 From: Philippe Martin Date: Mon, 22 Jan 2024 11:59:45 +0100 Subject: [PATCH] send application tasks to frontend when updated (#102) * send application tasks to frontend when updated * Models page also uses the store * fix import --- .../src/managers/applicationManager.ts | 6 +++- .../src/registries/RecipeStatusRegistry.ts | 21 ++++++++++++- .../backend/src/registries/TaskRegistry.ts | 4 --- packages/backend/src/studio-api-impl.spec.ts | 2 -- packages/backend/src/studio-api-impl.ts | 11 +++---- packages/backend/src/studio.ts | 3 +- packages/frontend/src/pages/Models.svelte | 30 +++++++------------ packages/frontend/src/pages/Recipe.spec.ts | 3 ++ packages/frontend/src/pages/Recipe.svelte | 28 ++--------------- packages/frontend/src/stores/recipe.ts | 27 +++++++++++++++++ packages/shared/Messages.ts | 1 + packages/shared/src/StudioAPI.ts | 10 +------ 12 files changed, 76 insertions(+), 70 deletions(-) create mode 100644 packages/frontend/src/stores/recipe.ts diff --git a/packages/backend/src/managers/applicationManager.ts b/packages/backend/src/managers/applicationManager.ts index 08a72253a..85bf17003 100644 --- a/packages/backend/src/managers/applicationManager.ts +++ b/packages/backend/src/managers/applicationManager.ts @@ -256,11 +256,15 @@ export class ApplicationManager { } } + let previousProgressValue = -1; resp.on('data', chunk => { progress += chunk.length; const progressValue = (progress * 100) / totalFileSize; - taskUtil.setTaskProgress(modelId, progressValue); + if (progressValue === 100 || progressValue - previousProgressValue > 1) { + previousProgressValue = progressValue; + taskUtil.setTaskProgress(modelId, progressValue); + } // send progress in percentage (ex. 1.2%, 2.6%, 80.1%) to frontend //this.sendProgress(progressValue); diff --git a/packages/backend/src/registries/RecipeStatusRegistry.ts b/packages/backend/src/registries/RecipeStatusRegistry.ts index 0a8779817..e614bec27 100644 --- a/packages/backend/src/registries/RecipeStatusRegistry.ts +++ b/packages/backend/src/registries/RecipeStatusRegistry.ts @@ -18,11 +18,16 @@ import type { RecipeStatus } from '@shared/src/models/IRecipeStatus'; import type { TaskRegistry } from './TaskRegistry'; +import type { Webview } from '@podman-desktop/api'; +import { MSG_NEW_RECIPE_STATE } from '@shared/Messages'; export class RecipeStatusRegistry { private statuses: Map = new Map(); - constructor(private taskRegistry: TaskRegistry) {} + constructor( + private taskRegistry: TaskRegistry, + private webview: Webview, + ) {} setStatus(recipeId: string, status: RecipeStatus) { // Update the TaskRegistry @@ -30,9 +35,23 @@ export class RecipeStatusRegistry { status.tasks.map(task => this.taskRegistry.set(task)); } this.statuses.set(recipeId, status); + this.dispatchState().catch((err: unknown) => { + console.error('error dispatching recipe statuses', err); + }); // we don't want to wait } getStatus(recipeId: string): RecipeStatus | undefined { return this.statuses.get(recipeId); } + + getStatuses(): Map { + return this.statuses; + } + + private async dispatchState() { + await this.webview.postMessage({ + id: MSG_NEW_RECIPE_STATE, + body: this.statuses, + }); + } } diff --git a/packages/backend/src/registries/TaskRegistry.ts b/packages/backend/src/registries/TaskRegistry.ts index 7c7f1cf6e..31bea2a72 100644 --- a/packages/backend/src/registries/TaskRegistry.ts +++ b/packages/backend/src/registries/TaskRegistry.ts @@ -21,10 +21,6 @@ import type { Task } from '@shared/src/models/ITask'; export class TaskRegistry { private tasks: Map = new Map(); - getTasksByLabel(label: string): Task[] { - return Array.from(this.tasks.values()).filter(task => label in (task.labels || {})); - } - set(task: Task) { this.tasks.set(task.id, task); } diff --git a/packages/backend/src/studio-api-impl.spec.ts b/packages/backend/src/studio-api-impl.spec.ts index 71c31bbd6..6d8d9cd84 100644 --- a/packages/backend/src/studio-api-impl.spec.ts +++ b/packages/backend/src/studio-api-impl.spec.ts @@ -25,7 +25,6 @@ import type { ApplicationManager } from './managers/applicationManager'; import type { RecipeStatusRegistry } from './registries/RecipeStatusRegistry'; import { StudioApiImpl } from './studio-api-impl'; import type { PlayGroundManager } from './managers/playground'; -import type { TaskRegistry } from './registries/TaskRegistry'; import type { Webview } from '@podman-desktop/api'; import * as fs from 'node:fs'; @@ -75,7 +74,6 @@ beforeEach(async () => { appUserDirectory, } as unknown as ApplicationManager, {} as unknown as RecipeStatusRegistry, - {} as unknown as TaskRegistry, {} as unknown as PlayGroundManager, catalogManager, ); diff --git a/packages/backend/src/studio-api-impl.ts b/packages/backend/src/studio-api-impl.ts index a36f268e5..63b481aee 100644 --- a/packages/backend/src/studio-api-impl.ts +++ b/packages/backend/src/studio-api-impl.ts @@ -21,8 +21,6 @@ import type { ApplicationManager } from './managers/applicationManager'; import type { RecipeStatusRegistry } from './registries/RecipeStatusRegistry'; import type { RecipeStatus } from '@shared/src/models/IRecipeStatus'; import type { ModelInfo } from '@shared/src/models/IModelInfo'; -import type { TaskRegistry } from './registries/TaskRegistry'; -import type { Task } from '@shared/src/models/ITask'; import type { PlayGroundManager } from './managers/playground'; import * as podmanDesktopApi from '@podman-desktop/api'; import type { QueryState } from '@shared/src/models/IPlaygroundQueryState'; @@ -36,7 +34,6 @@ export class StudioApiImpl implements StudioAPI { constructor( private applicationManager: ApplicationManager, private recipeStatusRegistry: RecipeStatusRegistry, - private taskRegistry: TaskRegistry, private playgroundManager: PlayGroundManager, private catalogManager: CatalogManager, ) {} @@ -53,6 +50,10 @@ export class StudioApiImpl implements StudioAPI { return this.recipeStatusRegistry.getStatus(recipeId); } + async getPullingStatuses(): Promise> { + return this.recipeStatusRegistry.getStatuses(); + } + async getModelById(modelId: string): Promise { // TODO: move logic to catalog manager const model = this.catalogManager.getModels().find(m => modelId === m.id); @@ -83,10 +84,6 @@ export class StudioApiImpl implements StudioAPI { return this.catalogManager.getModels().filter(m => localIds.includes(m.id)); } - async getTasksByLabel(label: string): Promise { - return this.taskRegistry.getTasksByLabel(label); - } - async startPlayground(modelId: string): Promise { // TODO: improve the following const localModelInfo = this.applicationManager.getLocalModels().filter(m => m.id === modelId); diff --git a/packages/backend/src/studio.ts b/packages/backend/src/studio.ts index 15db733c0..87bb917bd 100644 --- a/packages/backend/src/studio.ts +++ b/packages/backend/src/studio.ts @@ -92,7 +92,7 @@ export class Studio { this.rpcExtension = new RpcExtension(this.#panel.webview); const gitManager = new GitManager(); const taskRegistry = new TaskRegistry(); - const recipeStatusRegistry = new RecipeStatusRegistry(taskRegistry); + const recipeStatusRegistry = new RecipeStatusRegistry(taskRegistry, this.#panel.webview); const applicationManager = new ApplicationManager(gitManager, recipeStatusRegistry, this.#extensionContext); this.playgroundManager = new PlayGroundManager(this.#panel.webview); // Create catalog manager, responsible for loading the catalog files and watching for changes @@ -102,7 +102,6 @@ export class Studio { this.studioApi = new StudioApiImpl( applicationManager, recipeStatusRegistry, - taskRegistry, this.playgroundManager, this.catalogManager, ); diff --git a/packages/frontend/src/pages/Models.svelte b/packages/frontend/src/pages/Models.svelte index 63a69b44d..af6eca362 100644 --- a/packages/frontend/src/pages/Models.svelte +++ b/packages/frontend/src/pages/Models.svelte @@ -9,15 +9,11 @@ import ModelColumnRegistry from '../lib/table/model/ModelColumnRegistry.svelte'; import ModelColumnPopularity from '../lib/table/model/ModelColumnPopularity.svelte'; import ModelColumnLicense from '../lib/table/model/ModelColumnLicense.svelte'; import ModelColumnHw from '../lib/table/model/ModelColumnHW.svelte'; -import { onDestroy, onMount } from 'svelte'; -import { studioClient } from '/@/utils/client'; -import type { Category } from '@shared/models/ICategory'; -import type { Task } from '@shared/models/ITask'; +import type { Task } from '@shared/src/models/ITask'; import TasksProgress from '/@/lib/progress/TasksProgress.svelte'; -import { faRefresh } from '@fortawesome/free-solid-svg-icons'; import Card from '/@/lib/Card.svelte'; -import Button from '/@/lib/button/Button.svelte'; -import LinearProgress from '/@/lib/progress/LinearProgress.svelte'; + import { modelsPulling } from '../stores/recipe'; + import { onMount } from 'svelte'; const columns: Column[] = [ new Column('Name', { width: '4fr', renderer: ModelColumnName }), @@ -29,7 +25,6 @@ const columns: Column[] = [ const row = new Row({}); let loading: boolean = true; -let intervalId: ReturnType | undefined = undefined; let tasks: Task[] = []; let models: ModelInfo[] = []; @@ -46,31 +41,28 @@ function filterModels(): void { } return previousValue; }, [] as string[]); - filteredModels = models.filter((model) => !(model.id in modelsId)); + filteredModels = models.filter((model) => !modelsId.includes(model.id)); } onMount(() => { // Pulling update - intervalId = setInterval(async () => { - tasks = await studioClient.getTasksByLabel("model-pulling"); + const modelsPullingUnsubscribe = modelsPulling.subscribe(runningTasks => { + tasks = runningTasks; loading = false; filterModels(); - }, 1000); + }); // Subscribe to the models store - return localModels.subscribe((value) => { + const localModelsUnsubscribe = localModels.subscribe((value) => { models = value; filterModels(); }) -}); -onDestroy(() => { - if(intervalId !== undefined) { - clearInterval(intervalId); - intervalId = undefined; + return () => { + modelsPullingUnsubscribe(); + localModelsUnsubscribe(); } }); - diff --git a/packages/frontend/src/pages/Recipe.spec.ts b/packages/frontend/src/pages/Recipe.spec.ts index a12e52b3b..fdbf2cd3d 100644 --- a/packages/frontend/src/pages/Recipe.spec.ts +++ b/packages/frontend/src/pages/Recipe.spec.ts @@ -6,6 +6,7 @@ import Recipe from './Recipe.svelte'; const mocks = vi.hoisted(() => { return { getCatalogMock: vi.fn(), + getPullingStatusesMock: vi.fn(), }; }); @@ -13,6 +14,7 @@ vi.mock('../utils/client', async () => { return { studioClient: { getCatalog: mocks.getCatalogMock, + getPullingStatuses: mocks.getPullingStatusesMock, }, rpcBrowser: { subscribe: () => { @@ -29,6 +31,7 @@ test('should display recipe information', async () => { expect(recipe).not.toBeUndefined(); mocks.getCatalogMock.mockResolvedValue(catalog); + mocks.getPullingStatusesMock.mockResolvedValue(new Map()); render(Recipe, { recipeId: 'recipe 1', }); diff --git a/packages/frontend/src/pages/Recipe.svelte b/packages/frontend/src/pages/Recipe.svelte index 635c64217..3736ed6ff 100644 --- a/packages/frontend/src/pages/Recipe.svelte +++ b/packages/frontend/src/pages/Recipe.svelte @@ -1,6 +1,5 @@