From 47374127cfe46f6bcd9db9115241b42c1c62a2e2 Mon Sep 17 00:00:00 2001 From: "Ricardo M." Date: Tue, 23 Jul 2024 16:46:19 -0400 Subject: [PATCH] feat(vscode): Provide SettingsAdapter for VSCode At the moment, the Kaoto settings are passed individually to VSCode, meaning that in case we want to add new settings, we need to pass individual parameters to the `KaotoEditorApp` class. This commit provides a SettingsAdapter so every new setting will be added directly to the adapter. relates: https://issues.redhat.com/browse/KTO-441 relates: https://github.com/KaotoIO/kaoto/pull/1221 --- packages/ui/src/models-api.ts | 8 +++- .../settings/abstract-settings-adapter.ts | 6 --- .../settings/default-settings-adapter.ts | 18 ++++---- packages/ui/src/models/settings/index.ts | 1 - .../localstorage-settings-adapter.test.ts | 39 ++++++++++++++++ .../settings/localstorage-settings-adapter.ts | 14 +++--- .../ui/src/models/settings/settings.model.ts | 17 ++++--- .../multiplying-architecture/KaotoBridge.tsx | 10 ++--- .../KaotoEditorApp.tsx | 45 ++++++++++--------- .../KaotoEditorChannelApi.ts | 10 +++++ .../KaotoEditorFactory.ts | 25 ++++++++--- 11 files changed, 130 insertions(+), 63 deletions(-) delete mode 100644 packages/ui/src/models/settings/abstract-settings-adapter.ts create mode 100644 packages/ui/src/models/settings/localstorage-settings-adapter.test.ts diff --git a/packages/ui/src/models-api.ts b/packages/ui/src/models-api.ts index 774040c36..65c7b99c9 100644 --- a/packages/ui/src/models-api.ts +++ b/packages/ui/src/models-api.ts @@ -1,2 +1,6 @@ -/** Models components */ -export * from './models'; +/** + * Models components + * + * This file shouldn't export anything other than models, for instance, no components, no hooks, etc. + */ +export * from './models/settings'; diff --git a/packages/ui/src/models/settings/abstract-settings-adapter.ts b/packages/ui/src/models/settings/abstract-settings-adapter.ts deleted file mode 100644 index 616dc7d7a..000000000 --- a/packages/ui/src/models/settings/abstract-settings-adapter.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { SettingsModel } from './settings.model'; - -export abstract class AbstractSettingsAdapter { - abstract getSettings(): SettingsModel; - abstract saveSettings(settings: SettingsModel): void; -} diff --git a/packages/ui/src/models/settings/default-settings-adapter.ts b/packages/ui/src/models/settings/default-settings-adapter.ts index 0186f3811..048ca31e5 100644 --- a/packages/ui/src/models/settings/default-settings-adapter.ts +++ b/packages/ui/src/models/settings/default-settings-adapter.ts @@ -1,15 +1,17 @@ -import { ROOT_PATH, setValue } from '../../utils'; -import { AbstractSettingsAdapter } from './abstract-settings-adapter'; -import { SettingsModel } from './settings.model'; +import { AbstractSettingsAdapter, ISettingsModel, SettingsModel } from './settings.model'; -export class DefaultSettingsAdapter extends AbstractSettingsAdapter { - private readonly defaultSettings = new SettingsModel(); +export class DefaultSettingsAdapter implements AbstractSettingsAdapter { + private settings: ISettingsModel; + + constructor(settings?: Partial) { + this.settings = new SettingsModel(settings); + } getSettings() { - return this.defaultSettings; + return this.settings; } - saveSettings(settings: SettingsModel) { - setValue(this.defaultSettings, ROOT_PATH, settings); + saveSettings(settings: ISettingsModel) { + Object.assign(this.settings, settings); } } diff --git a/packages/ui/src/models/settings/index.ts b/packages/ui/src/models/settings/index.ts index 1bb44618f..09c0a85de 100644 --- a/packages/ui/src/models/settings/index.ts +++ b/packages/ui/src/models/settings/index.ts @@ -1,3 +1,2 @@ -export * from './abstract-settings-adapter'; export * from './default-settings-adapter'; export * from './settings.model'; diff --git a/packages/ui/src/models/settings/localstorage-settings-adapter.test.ts b/packages/ui/src/models/settings/localstorage-settings-adapter.test.ts new file mode 100644 index 000000000..b78403713 --- /dev/null +++ b/packages/ui/src/models/settings/localstorage-settings-adapter.test.ts @@ -0,0 +1,39 @@ +import { LocalStorageKeys } from '../local-storage-keys'; +import { LocalStorageSettingsAdapter } from './localstorage-settings-adapter'; +import { SettingsModel } from './settings.model'; + +describe('LocalStorageSettingsAdapter', () => { + it('should create an instance with the default settings', () => { + const adapter = new LocalStorageSettingsAdapter(); + + expect(adapter.getSettings()).toEqual(new SettingsModel()); + }); + + it('should save and retrieve settings', () => { + const adapter = new LocalStorageSettingsAdapter(); + const newSettings: SettingsModel = { catalogUrl: 'http://example.com' }; + + adapter.saveSettings(newSettings); + + expect(adapter.getSettings()).toEqual(newSettings); + }); + + it('should retrieve the saved settings from localStorage after creating a new instance', () => { + const localStorageGetItemSpy = jest.spyOn(Storage.prototype, 'getItem'); + + new LocalStorageSettingsAdapter(); + + expect(localStorageGetItemSpy).toHaveBeenCalledWith(LocalStorageKeys.Settings); + }); + + it('should save the settings to localStorage', () => { + const localStorageSetItemSpy = jest.spyOn(Storage.prototype, 'setItem'); + + const adapter = new LocalStorageSettingsAdapter(); + const newSettings: SettingsModel = { catalogUrl: 'http://example.com' }; + + adapter.saveSettings(newSettings); + + expect(localStorageSetItemSpy).toHaveBeenCalledWith(LocalStorageKeys.Settings, JSON.stringify(newSettings)); + }); +}); diff --git a/packages/ui/src/models/settings/localstorage-settings-adapter.ts b/packages/ui/src/models/settings/localstorage-settings-adapter.ts index 93c4c3ce3..18697f74e 100644 --- a/packages/ui/src/models/settings/localstorage-settings-adapter.ts +++ b/packages/ui/src/models/settings/localstorage-settings-adapter.ts @@ -1,23 +1,21 @@ import { LocalStorageKeys } from '../local-storage-keys'; -import { AbstractSettingsAdapter } from './abstract-settings-adapter'; -import { SettingsModel } from './settings.model'; +import { AbstractSettingsAdapter, ISettingsModel, SettingsModel } from './settings.model'; -export class LocalStorageSettingsAdapter extends AbstractSettingsAdapter { - private readonly settings: SettingsModel; +export class LocalStorageSettingsAdapter implements AbstractSettingsAdapter { + private settings: ISettingsModel; constructor() { - super(); - const rawSettings = localStorage.getItem(LocalStorageKeys.Settings) ?? '{}'; const parsedSettings = JSON.parse(rawSettings); this.settings = new SettingsModel(parsedSettings); } - getSettings(): SettingsModel { + getSettings(): ISettingsModel { return this.settings; } - saveSettings(settings: SettingsModel): void { + saveSettings(settings: ISettingsModel): void { localStorage.setItem(LocalStorageKeys.Settings, JSON.stringify(settings)); + this.settings = { ...settings }; } } diff --git a/packages/ui/src/models/settings/settings.model.ts b/packages/ui/src/models/settings/settings.model.ts index 878cb5365..b8f570ac9 100644 --- a/packages/ui/src/models/settings/settings.model.ts +++ b/packages/ui/src/models/settings/settings.model.ts @@ -1,11 +1,16 @@ -const DEFAULT_SETTINGS: SettingsModel = { - catalogUrl: '', -}; +export interface ISettingsModel { + catalogUrl: string; +} + +export interface AbstractSettingsAdapter { + getSettings(): ISettingsModel; + saveSettings(settings: ISettingsModel): void; +} -export class SettingsModel { +export class SettingsModel implements ISettingsModel { catalogUrl: string = ''; - constructor(options: Partial = {}) { - Object.assign(this, DEFAULT_SETTINGS, options); + constructor(options: Partial = {}) { + Object.assign(this, options); } } diff --git a/packages/ui/src/multiplying-architecture/KaotoBridge.tsx b/packages/ui/src/multiplying-architecture/KaotoBridge.tsx index cea14b646..7f88ca676 100644 --- a/packages/ui/src/multiplying-architecture/KaotoBridge.tsx +++ b/packages/ui/src/multiplying-architecture/KaotoBridge.tsx @@ -9,6 +9,7 @@ import { CatalogLoaderProvider } from '../providers/catalog.provider'; import { DeleteModalContextProvider } from '../providers/delete-modal.provider'; import { RuntimeProvider } from '../providers/runtime.provider'; import { SchemasLoaderProvider } from '../providers/schemas.provider'; +import { SettingsContext } from '../providers/settings.provider'; import { SourceCodeApiContext } from '../providers/source-code.provider'; import { VisibleFlowsProvider } from '../providers/visible-flows.provider'; import { EventNotifier } from '../utils'; @@ -45,11 +46,6 @@ interface KaotoBridgeProps { * ChannelType where the component is running. */ channelType: ChannelType; - - /** - * Catalog URL to load the components and schemas. - */ - catalogUrl: string; } export const KaotoBridge = forwardRef>((props, forwardedRef) => { @@ -57,6 +53,8 @@ export const KaotoBridge = forwardRef(''); + const settingsAdapter = useContext(SettingsContext); + const catalogUrl = settingsAdapter.getSettings().catalogUrl; /** * Callback is exposed to the Channel that is called when a new file is opened. @@ -145,7 +143,7 @@ export const KaotoBridge = forwardRef - + diff --git a/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx b/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx index bb981052e..bba3141cb 100644 --- a/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx +++ b/packages/ui/src/multiplying-architecture/KaotoEditorApp.tsx @@ -8,7 +8,9 @@ import { import { Notification } from '@kie-tools-core/notifications/dist/api'; import '@patternfly/react-core/dist/styles/base.css'; // This import needs to be first import { RefObject, createRef } from 'react'; +import { AbstractSettingsAdapter } from '../models/settings'; import { EntitiesProvider } from '../providers/entities.provider'; +import { SettingsProvider } from '../providers/settings.provider'; import { SourceCodeProvider } from '../providers/source-code.provider'; import { KaotoBridge } from './KaotoBridge'; import { KaotoEditor } from './KaotoEditor'; @@ -23,7 +25,7 @@ export class KaotoEditorApp implements Editor { constructor( private readonly envelopeContext: KogitoEditorEnvelopeContextType, private readonly initArgs: EditorInitArgs, - private readonly catalogUrl: string, + private readonly settingsAdapter: AbstractSettingsAdapter, ) { this.editorRef = createRef(); } @@ -75,26 +77,27 @@ export class KaotoEditorApp implements Editor { return ( - this.envelopeContext.channelApi.notifications.kogitoEditor_ready.send()} - onNewEdit={(edit) => { - this.envelopeContext.channelApi.notifications.kogitoWorkspace_newEdit.send(edit); - }} - setNotifications={(path, notifications) => - this.envelopeContext.channelApi.notifications.kogitoNotifications_setNotifications.send( - path, - notifications, - ) - } - onStateControlCommandUpdate={(command) => - this.envelopeContext.channelApi.notifications.kogitoEditor_stateControlCommandUpdate.send(command) - } - catalogUrl={this.catalogUrl} - > - - + + this.envelopeContext.channelApi.notifications.kogitoEditor_ready.send()} + onNewEdit={(edit) => { + this.envelopeContext.channelApi.notifications.kogitoWorkspace_newEdit.send(edit); + }} + setNotifications={(path, notifications) => + this.envelopeContext.channelApi.notifications.kogitoNotifications_setNotifications.send( + path, + notifications, + ) + } + onStateControlCommandUpdate={(command) => + this.envelopeContext.channelApi.notifications.kogitoEditor_stateControlCommandUpdate.send(command) + } + > + + + ); diff --git a/packages/ui/src/multiplying-architecture/KaotoEditorChannelApi.ts b/packages/ui/src/multiplying-architecture/KaotoEditorChannelApi.ts index c7f11fc42..c6c8b6c2a 100644 --- a/packages/ui/src/multiplying-architecture/KaotoEditorChannelApi.ts +++ b/packages/ui/src/multiplying-architecture/KaotoEditorChannelApi.ts @@ -1,5 +1,15 @@ import { KogitoEditorChannelApi } from '@kie-tools-core/editor/dist/api'; +import { ISettingsModel } from '../models/settings'; export interface KaotoEditorChannelApi extends KogitoEditorChannelApi { + /** + * @deprecated Use `getVSCodeKaotoSettings().catalogUrl` instead + * Returns the URL of the catalog. + */ getCatalogURL(): Promise; + + /** + * Returns the Kaoto VSCode settings defined. + */ + getVSCodeKaotoSettings(): Promise; } diff --git a/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts b/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts index 48779c088..5b10328c1 100644 --- a/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts +++ b/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts @@ -1,24 +1,39 @@ -import { KaotoEditorApp } from './KaotoEditorApp'; import { Editor, EditorFactory, EditorInitArgs, KogitoEditorEnvelopeContextType, } from '@kie-tools-core/editor/dist/api'; -import { KaotoEditorChannelApi } from './KaotoEditorChannelApi'; +import { DefaultSettingsAdapter } from '../models'; import { CatalogSchemaLoader, isDefined } from '../utils'; +import { KaotoEditorApp } from './KaotoEditorApp'; +import { KaotoEditorChannelApi } from './KaotoEditorChannelApi'; export class KaotoEditorFactory implements EditorFactory { public async createEditor( envelopeContext: KogitoEditorEnvelopeContextType, initArgs: EditorInitArgs, ): Promise { - let catalogUrl = await envelopeContext.channelApi.requests.getCatalogURL(); + const settings = await envelopeContext.channelApi.requests.getVSCodeKaotoSettings(); + const settingsAdapter = new DefaultSettingsAdapter(settings); + this.updateCatalogUrl(settingsAdapter, initArgs); + + return Promise.resolve(new KaotoEditorApp(envelopeContext, initArgs, settingsAdapter)); + } + + /** + * Updates the catalog URL in the settings if it is not defined, to include the embedded catalog. + * It uses the resourcesPathPrefix from the initArgs to build the default catalog URL. + * + * @param settingsAdapter The settings adapter to update the catalog URL + * @param initArgs The init args to get the resources path prefix + */ + private updateCatalogUrl(settingsAdapter: DefaultSettingsAdapter, initArgs: EditorInitArgs) { + let catalogUrl = settingsAdapter.getSettings().catalogUrl; if (!isDefined(catalogUrl) || catalogUrl === '') { catalogUrl = `${initArgs.resourcesPathPrefix}${CatalogSchemaLoader.DEFAULT_CATALOG_PATH.replace('.', '')}`; + settingsAdapter.saveSettings({ ...settingsAdapter.getSettings(), catalogUrl }); } - - return Promise.resolve(new KaotoEditorApp(envelopeContext, initArgs, catalogUrl)); } }