Skip to content

Commit

Permalink
Merge pull request #47 from fleetbase/dev-v0.2.13
Browse files Browse the repository at this point in the history
`UniverseService` can now handle all engine booting from either regis…
  • Loading branch information
roncodes authored Jul 17, 2024
2 parents 82d2e57 + dd3a624 commit db536b4
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 2 deletions.
154 changes: 153 additions & 1 deletion addon/services/universe.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { A, isArray } from '@ember/array';
import { later } from '@ember/runloop';
import { dasherize, camelize } from '@ember/string';
import { getOwner } from '@ember/application';
import { assert } from '@ember/debug';
import { assert, debug } from '@ember/debug';
import RSVP from 'rsvp';
import loadInstalledExtensions from '../utils/load-installed-extensions';
import loadExtensions from '../utils/load-extensions';
import getWithDefault from '../utils/get-with-default';

export default class UniverseService extends Service.extend(Evented) {
@service router;
Expand Down Expand Up @@ -1269,6 +1272,155 @@ export default class UniverseService extends Service.extend(Evented) {
return null;
}

/**
* Boot all installed engines, ensuring dependencies are resolved.
*
* This method attempts to boot all installed engines by first checking if all
* their dependencies are already booted. If an engine has dependencies that
* are not yet booted, it is deferred and retried after its dependencies are
* booted. If some dependencies are never booted, an error is logged.
*
* @method bootEngines
* @param {ApplicationInstance|null} owner - The Ember ApplicationInstance that owns the engines.
* @return {void}
*/
bootEngines(owner = null) {
const booted = [];
const pending = [];

// If no owner provided use the owner of this service
if (owner === null) {
owner = getOwner(this);
}

const tryBootEngine = (extension) => {
this.loadEngine(extension.name).then((engineInstance) => {
if (engineInstance.base && engineInstance.base.setupExtension) {
const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []);

// Check if all dependency engines are booted
const allDependenciesBooted = engineDependencies.every((dep) => booted.includes(dep));

if (!allDependenciesBooted) {
pending.push({ extension, engineInstance });
return;
}

engineInstance.base.setupExtension(owner, engineInstance, this);
booted.push(extension.name);
debug(`Booted : ${extension.name}`);

// Try booting pending engines again
tryBootPendingEngines();
}
});
};

const tryBootPendingEngines = () => {
const stillPending = [];

pending.forEach(({ extension, engineInstance }) => {
const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []);
const allDependenciesBooted = engineDependencies.every((dep) => booted.includes(dep));

if (allDependenciesBooted) {
engineInstance.base.setupExtension(owner, engineInstance, this);
booted.push(extension.name);
debug(`Booted : ${extension.name}`);
} else {
stillPending.push({ extension, engineInstance });
}
});

// If no progress was made, log an error in debug/development mode
assert('Some engines have unmet dependencies and cannot be booted:', pending.length === stillPending.length);

pending.length = 0;
pending.push(...stillPending);
};

loadInstalledExtensions().then((extensions) => {
extensions.forEach((extension) => {
tryBootEngine(extension);
});
});
}

/**
* Boots all installed engines, ensuring dependencies are resolved.
*
* This method loads all installed extensions and then attempts to boot each engine.
* For each extension, it loads the engine and, if the engine has a `setupExtension`
* method in its base, it calls this method to complete the setup. This function ensures
* that dependencies are resolved before booting the engines. If some dependencies are
* never booted, an error is logged.
*
* @method legacyBootEngines
* @param {ApplicationInstance|null} owner - The Ember ApplicationInstance that owns the engines.
* @return {void}
*/
legacyBootEngines(owner = null) {
const booted = [];
const pending = [];

// If no owner provided use the owner of this service
if (owner === null) {
owner = getOwner(this);
}

const tryBootEngine = (extension) => {
this.loadEngine(extension.name).then((engineInstance) => {
if (engineInstance.base && engineInstance.base.setupExtension) {
const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []);

// Check if all dependency engines are booted
const allDependenciesBooted = engineDependencies.every((dep) => booted.includes(dep));

if (!allDependenciesBooted) {
pending.push({ extension, engineInstance });
return;
}

engineInstance.base.setupExtension(owner, engineInstance, this);
booted.push(extension.name);
debug(`Booted : ${extension.name}`);

// Try booting pending engines again
tryBootPendingEngines();
}
});
};

const tryBootPendingEngines = () => {
const stillPending = [];

pending.forEach(({ extension, engineInstance }) => {
const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []);
const allDependenciesBooted = engineDependencies.every((dep) => booted.includes(dep));

if (allDependenciesBooted) {
engineInstance.base.setupExtension(owner, engineInstance, this);
booted.push(extension.name);
debug(`Booted : ${extension.name}`);
} else {
stillPending.push({ extension, engineInstance });
}
});

// If no progress was made, log an error in debug/development mode
assert('Some engines have unmet dependencies and cannot be booted:', pending.length === stillPending.length);

pending.length = 0;
pending.push(...stillPending);
};

loadExtensions().then((extensions) => {
extensions.forEach((extension) => {
tryBootEngine(extension);
});
});
}

/**
* Alias for intl service `t`
*
Expand Down
63 changes: 63 additions & 0 deletions addon/utils/fleetbase-api-fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import config from 'ember-get-config';

export default async function fleetbaseApiFetch(method, uri, params = {}, fetchOptions = {}) {
// Prepare base URL
const baseUrl = `${config.API.host}/${fetchOptions.namespace ?? config.API.namespace}`;

// Initialize headers
const headers = {
'Content-Type': 'application/json',
};

// Check localStorage for the session data
const localStorageSession = JSON.parse(window.localStorage.getItem('ember_simple_auth-session'));
let token;
if (localStorageSession) {
const { authenticated } = localStorageSession;
if (authenticated) {
token = authenticated.token;
}
}

// Set Authorization header if token is available
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}

// Configure request options
const options = {
method,
headers,
};

// Handle params based on method
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) && params) {
options.body = JSON.stringify(params);
} else if (method === 'GET' && params) {
// Add params to URL for GET requests
const urlParams = new URLSearchParams(params).toString();
uri += `?${urlParams}`;
}

try {
// Make the fetch request
const response = await fetch(`${baseUrl}/${uri}`, options);

// Check if the response is OK (status in the range 200-299)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

// Parse and return the JSON response
return await response.json();
} catch (error) {
// If a fallback response is provided use it instead
if (fetchOptions && fetchOptions.fallbackResponse !== undefined) {
return fetchOptions.fallbackResponse;
}

// Handle errors (network errors, JSON parsing errors, etc.)
console.error('Error making request:', error);
throw error;
}
}
16 changes: 16 additions & 0 deletions addon/utils/load-installed-extensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import loadExtensions from '../utils/load-extensions';
import fleetbaseApiFetch from '../utils/fleetbase-api-fetch';

export default async function loadInstalledExtensions() {
const CORE_ENGINES = ['@fleetbase/fleetops-engine', '@fleetbase/storefront-engine', '@fleetbase/registry-bridge-engine', '@fleetbase/dev-engine', '@fleetbase/iam-engine'];
const INDEXED_ENGINES = await loadExtensions();
const INSTALLED_ENGINES = await fleetbaseApiFetch('get', 'engines', {}, { namespace: '~registry/v1', fallbackResponse: [] });

const isInstalledEngine = (engineName) => {
return CORE_ENGINES.includes(engineName) || INSTALLED_ENGINES.find((pkg) => pkg.name === engineName);
};

return INDEXED_ENGINES.filter((pkg) => {
return isInstalledEngine(pkg.name);
});
}
1 change: 1 addition & 0 deletions app/utils/fleetbase-api-fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-core/utils/fleetbase-api-fetch';
1 change: 1 addition & 0 deletions app/utils/load-installed-extensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-core/utils/load-installed-extensions';
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fleetbase/ember-core",
"version": "0.2.12",
"version": "0.2.13",
"description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
"keywords": [
"fleetbase-core",
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/utils/fleetbase-api-fetch-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import fleetbaseApiFetch from 'dummy/utils/fleetbase-api-fetch';
import { module, test } from 'qunit';

module('Unit | Utility | fleetbase-api-fetch', function () {
// TODO: Replace this with your real tests.
test('it works', function (assert) {
let result = fleetbaseApiFetch();
assert.ok(result);
});
});
10 changes: 10 additions & 0 deletions tests/unit/utils/load-installed-extensions-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import loadInstalledExtensions from 'dummy/utils/load-installed-extensions';
import { module, test } from 'qunit';

module('Unit | Utility | load-installed-extensions', function () {
// TODO: Replace this with your real tests.
test('it works', function (assert) {
let result = loadInstalledExtensions();
assert.ok(result);
});
});

0 comments on commit db536b4

Please sign in to comment.