Skip to content

Commit

Permalink
critical patches and fixes for universe service, fetch service, and u…
Browse files Browse the repository at this point in the history
…tilities
  • Loading branch information
roncodes committed Dec 20, 2023
1 parent 3be4b02 commit efcb422
Show file tree
Hide file tree
Showing 21 changed files with 786 additions and 218 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ module.exports = {
'ember/no-incorrect-calls-with-inline-anonymous-functions': 'off',
'ember/no-private-routing-service': 'off',
'no-useless-escape': 'off',
'node/no-unpublished-require': [
'n/no-unpublished-require': [
'error',
{
allowModules: ['resolve', 'broccoli-funnel', 'broccoli-merge-trees'],
Expand Down
24 changes: 24 additions & 0 deletions addon/adapters/application.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import RESTAdapter from '@ember-data/adapter/rest';
import AdapterError from '@ember-data/adapter/error';
import { tracked } from '@glimmer/tracking';
import { inject as service } from '@ember/service';
import { storageFor } from 'ember-local-storage';
Expand Down Expand Up @@ -145,4 +146,27 @@ export default class ApplicationAdapter extends RESTAdapter {
pathForType(type) {
return dasherize(pluralize(type));
}

/**
* Handles the response from an AJAX request in an Ember application.
*
* @param {number} status - The HTTP status code of the response.
* @param {object} headers - The headers of the response.
* @param {object} payload - The payload of the response.
* @return {Object | AdapterError} response - Returns a new `AdapterError` instance with detailed error information if the response is invalid; otherwise, it returns the result of the superclass's `handleResponse` method.
*
* This method first normalizes the error response and generates a detailed message.
* It then checks if the response is invalid based on the status code. If invalid, it constructs an `AdapterError` with the normalized errors and detailed message.
* For valid responses, it delegates the handling to the superclass's `handleResponse` method.
*/
handleResponse(status, headers, payload) {
let errors = this.normalizeErrorResponse(status, headers, payload);
let detailedMessage = this.generatedDetailedMessage(...arguments);

if (this.isInvalid(status, headers, payload)) {
return new AdapterError(errors, detailedMessage);
}

return super.handleResponse(...arguments);
}
}
10 changes: 8 additions & 2 deletions addon/services/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,12 @@ export default class FetchService extends Service {
const { queue } = file;
const headers = this.getHeaders();

// make sure this task runs once for this file in correct state
// this can occur when the task is called twice when upload button exists inside upload dropzone
if (['queued', 'failed', 'timed_out', 'aborted'].indexOf(file.state) === -1) {
return;
}

// remove Content-Type header
delete headers['Content-Type'];

Expand All @@ -539,7 +545,7 @@ export default class FetchService extends Service {
})
.then((response) => response.json())
.catch((error) => {
this.notifications.serverError(error, `Upload failed.`);
this.notifications.serverError(error, 'File upload failed.');
});

const model = this.store.push(this.store.normalize('file', upload.file));
Expand All @@ -552,7 +558,7 @@ export default class FetchService extends Service {
return model;
} catch (error) {
queue.remove(file);
this.notifications.serverError(error, `Upload failed.`);
this.notifications.serverError(error, 'File upload failed.');

if (typeof errorCallback === 'function') {
errorCallback(error);
Expand Down
133 changes: 129 additions & 4 deletions addon/services/universe.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,90 @@ export default class UniverseService extends Service.extend(Evented) {
if (engineInstance) {
const config = engineInstance.resolveRegistration('config:environment');

if (config && typeof config.mountedEngineRoutePrefix === 'string') {
return this.router.transitionTo(`${config.mountedEngineRoutePrefix}${route}`, ...args);
if (config) {
let mountedEngineRoutePrefix = config.mountedEngineRoutePrefix;

if (!mountedEngineRoutePrefix) {
mountedEngineRoutePrefix = this._mountPathFromEngineName(engineName);
}

if (!mountedEngineRoutePrefix.endsWith('.')) {
mountedEngineRoutePrefix = mountedEngineRoutePrefix + '.';
}

return this.router.transitionTo(`${mountedEngineRoutePrefix}${route}`, ...args);
}
}

return this.router.transitionTo(route, ...args);
}

/**
* Retrieves the mount point of a specified engine by its name.
* @param {string} engineName - The name of the engine for which to get the mount point.
* @returns {string|null} The mount point of the engine or null if not found.
*/
getEngineMountPoint(engineName) {
const engineInstance = this.getEngineInstance(engineName);
return this._getMountPointFromEngineInstance(engineInstance);
}

/**
* Determines the mount point from an engine instance by reading its configuration.
* @param {object} engineInstance - The instance of the engine.
* @returns {string|null} The resolved mount point or null if the instance is undefined or the configuration is not set.
* @private
*/
_getMountPointFromEngineInstance(engineInstance) {
if (engineInstance) {
const config = engineInstance.resolveRegistration('config:environment');

if (config) {
let engineName = config.modulePrefix;
let mountedEngineRoutePrefix = config.mountedEngineRoutePrefix;

if (!mountedEngineRoutePrefix) {
mountedEngineRoutePrefix = this._mountPathFromEngineName(engineName);
}

if (!mountedEngineRoutePrefix.endsWith('.')) {
mountedEngineRoutePrefix = mountedEngineRoutePrefix + '.';
}

return mountedEngineRoutePrefix;
}
}

return null;
}

/**
* Extracts and formats the mount path from a given engine name.
*
* This function takes an engine name in the format '@scope/engine-name',
* extracts the 'engine-name' part, removes the '-engine' suffix if present,
* and formats it into a string that represents a console path.
*
* @param {string} engineName - The full name of the engine, typically in the format '@scope/engine-name'.
* @returns {string} A string representing the console path derived from the engine name.
* @example
* // returns 'console.some'
* _mountPathFromEngineName('@fleetbase/some-engine');
*/
_mountPathFromEngineName(engineName) {
let engineNameSegments = engineName.split('/');
let mountName = engineNameSegments[1];

if (typeof mountName !== 'string') {
mountName = engineNameSegments[0];
}

const mountPath = mountName.replace('-engine', '');
return `console.${mountPath}`;
}

/**
* Refreshes the current route.
*
Expand Down Expand Up @@ -804,7 +880,7 @@ export default class UniverseService extends Service.extend(Evented) {
loadEngine(name) {
const router = getOwner(this).lookup('router:main');
const instanceId = 'manual'; // Arbitrary instance id, should be unique per engine
const mountPoint = null; // No mount point for manually loaded engines
const mountPoint = this._mountPathFromEngineName(name); // No mount point for manually loaded engines

if (!router._enginePromises[name]) {
router._enginePromises[name] = Object.create(null);
Expand Down Expand Up @@ -853,7 +929,6 @@ export default class UniverseService extends Service.extend(Evented) {
assert("You attempted to load the engine '" + name + "', but the engine cannot be found.", owner.hasRegistration(`engine:${name}`));

let engineInstances = owner.lookup('router:main')._engineInstances;

if (!engineInstances[name]) {
engineInstances[name] = Object.create(null);
}
Expand All @@ -863,13 +938,63 @@ export default class UniverseService extends Service.extend(Evented) {
mountPoint,
});

// correct mountPoint using engine instance
let _mountPoint = this._getMountPointFromEngineInstance(engineInstance);
if (_mountPoint) {
engineInstance.mountPoint = _mountPoint;
}

// make sure to set dependencies from base instance
if (engineInstance.base) {
engineInstance.dependencies = this._setupEngineParentDependenciesBeforeBoot(engineInstance.base.dependencies);
}

// store loaded instance to engineInstances for booting
engineInstances[name][instanceId] = engineInstance;

return engineInstance.boot().then(() => {
return engineInstance;
});
}

_setupEngineParentDependenciesBeforeBoot(baseDependencies = {}) {
const dependencies = { ...baseDependencies };

// fix services
const servicesObject = {};
if (isArray(dependencies.services)) {
for (let i = 0; i < dependencies.services.length; i++) {
const service = dependencies.services.objectAt(i);

if (typeof service === 'object') {
Object.assign(servicesObject, service);
continue;
}

servicesObject[service] = service;
}
}

// fix external routes
const externalRoutesObject = {};
if (isArray(dependencies.externalRoutes)) {
for (let i = 0; i < dependencies.externalRoutes.length; i++) {
const externalRoute = dependencies.externalRoutes.objectAt(i);

if (typeof externalRoute === 'object') {
Object.assign(externalRoutesObject, externalRoute);
continue;
}

externalRoutesObject[externalRoute] = externalRoute;
}
}

dependencies.externalRoutes = externalRoutesObject;
dependencies.services = servicesObject;
return dependencies;
}

/**
* Retrieve an existing engine instance by its name and instanceId.
*
Expand Down
8 changes: 8 additions & 0 deletions addon/utils/get-mounted-engine-route-prefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function getMountedEngineRoutePrefix(defaultName, fleetbase = {}) {
let mountedEngineRoutePrefix = defaultName;
if (fleetbase && typeof fleetbase.route === 'string') {
mountedEngineRoutePrefix = fleetbase.route;
}

return `console.${mountedEngineRoutePrefix}.`;
}
12 changes: 12 additions & 0 deletions addon/utils/get-resource-name-from-transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function getResourceNameFromTransition(transition) {
const { to } = transition;

if (typeof to.name === 'string') {
let routePathSegments = to.name.split('.');
let resourceName = routePathSegments[3];

return resourceName;
}

return null;
}
11 changes: 11 additions & 0 deletions addon/utils/is-nested-route-transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { get } from '@ember/object';

export default function isNestedRouteTransition(transition) {
const toRoute = get(transition, 'to.name');
const fromRouteParent = get(transition, 'from.parent.name');
const toRouteParent = get(transition, 'to.parent.name');
const isNested = toRoute.startsWith(fromRouteParent);
const isMatchingParents = fromRouteParent && toRouteParent && fromRouteParent === toRouteParent;

return isNested || isMatchingParents;
}
2 changes: 1 addition & 1 deletion addon/utils/is-object.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function isObject(obj) {
return typeof obj === 'object';
return obj && typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Object]';
}
17 changes: 14 additions & 3 deletions addon/utils/map-engines.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import { dasherize } from '@ember/string';
import hostServices from '../exports/host-services';

function routeNameFromExtension(extension) {
const mountName = extension.name.split('/')[1];
const mountPath = mountName.replace('-engine', '');
export function getExtensionMountPath(extensionName) {
let extensionNameSegments = extensionName.split('/');
let mountName = extensionNameSegments[1];

if (typeof mountName !== 'string') {
mountName = extensionNameSegments[0];
}

return mountName.replace('-engine', '');
}

export function routeNameFromExtension(extension) {
const mountPath = getExtensionMountPath(extension.name);
let route = mountPath;

if (extension.fleetbase && extension.fleetbase.route) {
Expand All @@ -18,6 +28,7 @@ export default function mapEngines(extensions, withServices = []) {
const externalRoutes = {
console: 'console.home',
extensions: 'console.extensions',
notifications: 'console.notifications',
};

for (let i = 0; i < extensions.length; i++) {
Expand Down
9 changes: 9 additions & 0 deletions addon/utils/set-component-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { set } from '@ember/object';

export default function setComponentArg(component, property, value) {
if (value !== undefined) {
set(component, property, value);
}

return component;
}
1 change: 1 addition & 0 deletions app/utils/get-mounted-engine-route-prefix.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-core/utils/get-mounted-engine-route-prefix';
1 change: 1 addition & 0 deletions app/utils/get-resource-name-from-transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-core/utils/get-resource-name-from-transition';
1 change: 1 addition & 0 deletions app/utils/is-nested-route-transition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-core/utils/is-nested-route-transition';
1 change: 1 addition & 0 deletions app/utils/set-component-arg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@fleetbase/ember-core/utils/set-component-arg';
10 changes: 10 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ module.exports = {
useSessionSetupMethod: true,
};
}

if (app.options['ember-cli-notifications'] !== undefined) {
app.options['ember-cli-notifications'].autoClear = true;
app.options['ember-cli-notifications'].clearDuration = 1000 * 5;
} else {
app.options['ember-cli-notifications'] = {
autoClear: true,
clearDuration: 1000 * 5,
};
}
},

treeForPublic: function () {
Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@
"devDependencies": {
"@babel/eslint-parser": "^7.22.15",
"@babel/plugin-proposal-decorators": "^7.23.2",
"@ember-data/serializer": "^5.3.0",
"@ember-data/store": "^5.3.0",
"@ember/optional-features": "^2.0.0",
"@ember/test-helpers": "^3.2.0",
"@embroider/test-setup": "^3.0.2",
Expand All @@ -79,6 +77,7 @@
"ember-qunit": "^8.0.1",
"ember-resolver": "^11.0.1",
"ember-source": "~5.4.0",
"ember-data": "^4.12.5",
"ember-source-channel-url": "^3.0.0",
"ember-template-lint": "^5.11.2",
"ember-try": "^3.0.0",
Expand Down
Loading

0 comments on commit efcb422

Please sign in to comment.