diff --git a/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts b/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts index 5b10328c1..f1c9df14c 100644 --- a/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts +++ b/packages/ui/src/multiplying-architecture/KaotoEditorFactory.ts @@ -4,8 +4,8 @@ import { EditorInitArgs, KogitoEditorEnvelopeContextType, } from '@kie-tools-core/editor/dist/api'; -import { DefaultSettingsAdapter } from '../models'; -import { CatalogSchemaLoader, isDefined } from '../utils'; +import { DefaultSettingsAdapter, ISettingsModel, SettingsModel } from '../models'; +import { CatalogSchemaLoader, isDefined, promiseTimeout } from '../utils'; import { KaotoEditorApp } from './KaotoEditorApp'; import { KaotoEditorChannelApi } from './KaotoEditorChannelApi'; @@ -14,13 +14,40 @@ export class KaotoEditorFactory implements EditorFactory, initArgs: EditorInitArgs, ): Promise { - const settings = await envelopeContext.channelApi.requests.getVSCodeKaotoSettings(); + const settings = await this.getSettings(envelopeContext); + const settingsAdapter = new DefaultSettingsAdapter(settings); this.updateCatalogUrl(settingsAdapter, initArgs); return Promise.resolve(new KaotoEditorApp(envelopeContext, initArgs, settingsAdapter)); } + /** + * Get the settings from the envelope context + */ + private async getSettings( + envelopeContext: KogitoEditorEnvelopeContextType, + ): Promise { + let settings: ISettingsModel; + + try { + /** + * For non-implemented API methods, the promises won't resolve, for that reason + * we use a timeout to reject the promise and fallback to the previous API + */ + settings = await promiseTimeout(envelopeContext.channelApi.requests.getVSCodeKaotoSettings(), 500); + } catch (error) { + /** + * Reaching this point means that the new API is not available yet, + * so we fallback to the previous API + */ + const catalogUrl = await envelopeContext.channelApi.requests.getCatalogURL(); + settings = new SettingsModel({ catalogUrl }); + } + + return settings; + } + /** * 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. diff --git a/packages/ui/src/utils/index.ts b/packages/ui/src/utils/index.ts index 44fb2af7f..ae8ba10ad 100644 --- a/packages/ui/src/utils/index.ts +++ b/packages/ui/src/utils/index.ts @@ -12,6 +12,7 @@ export * from './set-value'; export * from './get-custom-schema-from-kamelet'; export * from './update-kamelet-from-custom-schema'; export * from './pipe-custom-schema'; +export * from './promise-timeout'; export * from './get-field-groups'; export * from './get-serialized-model'; export * from './get-user-updated-properties-schema'; diff --git a/packages/ui/src/utils/promise-timeout.test.ts b/packages/ui/src/utils/promise-timeout.test.ts new file mode 100644 index 000000000..fa8913744 --- /dev/null +++ b/packages/ui/src/utils/promise-timeout.test.ts @@ -0,0 +1,54 @@ +import { promiseTimeout } from './promise-timeout'; + +describe('promiseTimeout', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should resolve the promise if it resolves before the timeout', async () => { + const promise = Promise.resolve('foo'); + const result = await promiseTimeout(promise, 1_000); + + expect(result).toBe('foo'); + }); + + it('should reject the promise if it rejects before the timeout', async () => { + const promise = Promise.reject(new Error('bar')); + + await expect(promiseTimeout(promise, 1_000)).rejects.toThrow('bar'); + }); + + it('should resolve the promise with the defaultValue when provided, if it takes longer than the timeout', async () => { + const promise = new Promise((resolve) => { + setTimeout(() => { + resolve('baz'); + }, 1_000); + }); + + const promiseTimeoutResult = promiseTimeout(promise, 500, 'Lighting fast'); + + jest.advanceTimersByTime(500); + + const result = await promiseTimeoutResult; + + expect(result).toBe('Lighting fast'); + }); + + it('should reject the promise if it takes longer than the timeout', async () => { + const promise = new Promise((resolve) => { + setTimeout(() => { + resolve('baz'); + }, 1_000); + }); + + const promiseTimeoutResult = promiseTimeout(promise, 500); + + jest.advanceTimersByTime(500); + + await expect(promiseTimeoutResult).rejects.toThrow('Promise timed out'); + }); +}); diff --git a/packages/ui/src/utils/promise-timeout.ts b/packages/ui/src/utils/promise-timeout.ts new file mode 100644 index 000000000..4268cc94d --- /dev/null +++ b/packages/ui/src/utils/promise-timeout.ts @@ -0,0 +1,19 @@ +export const promiseTimeout: (promise: Promise, timeout: number, defaultValue?: T) => Promise = ( + promise: Promise, + timeout: number, + defaultValue?: T, +) => { + let timer: number; + + const timeoutPromise = new Promise((resolve, reject) => { + timer = setTimeout(() => { + if (defaultValue !== undefined) resolve(defaultValue); + + reject(new Error('Promise timed out')); + }, timeout) as unknown as number; + }); + + return Promise.race([promise, timeoutPromise]).finally(() => { + clearTimeout(timer); + }); +};