From 2146a838da7f82434e7a29525abf8740391df417 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" <me@ron.dev> Date: Wed, 25 Sep 2024 14:01:14 +0800 Subject: [PATCH 1/5] new container register utilites and optimization on current user service --- addon/services/current-user.js | 28 +++++++++++++++++---- addon/services/fetch.js | 1 + addon/utils/register-component.js | 8 ++++++ addon/utils/register-helper.js | 8 ++++++ app/utils/register-component.js | 1 + app/utils/register-helper.js | 1 + package.json | 2 +- tests/unit/utils/register-component-test.js | 10 ++++++++ tests/unit/utils/register-helper-test.js | 10 ++++++++ 9 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 addon/utils/register-component.js create mode 100644 addon/utils/register-helper.js create mode 100644 app/utils/register-component.js create mode 100644 app/utils/register-helper.js create mode 100644 tests/unit/utils/register-component-test.js create mode 100644 tests/unit/utils/register-helper-test.js diff --git a/addon/services/current-user.js b/addon/services/current-user.js index 726b603..804c02b 100644 --- a/addon/services/current-user.js +++ b/addon/services/current-user.js @@ -88,14 +88,26 @@ export default class CurrentUserService extends Service.extend(Evented) { this.set('user', user); this.trigger('user.loaded', user); + console.log(user); + // Set permissions this.permissions = this.getUserPermissions(user); // Set environment from user option this.theme.setEnvironment(); - // Load user preferces - await this.loadPreferences(); + // Set locale + if (user.locale) { + this.setLocale(user.locale); + } else { + await this.loadLocale(); + } + + // Load user whois data + await this.loadWhois(); + + // Load user organizations + await this.loadOrganizations(); // Optional callback if (typeof options?.onUserResolved === 'function') { @@ -118,9 +130,7 @@ export default class CurrentUserService extends Service.extend(Evented) { async loadLocale() { try { const { locale } = await this.fetch.get('users/locale'); - this.setOption('locale', locale); - this.intl.setLocale(locale); - this.locale = locale; + this.setLocale(locale); return locale; } catch (error) { @@ -207,6 +217,14 @@ export default class CurrentUserService extends Service.extend(Evented) { return this.getWhoisProperty(key); } + setLocale(locale) { + this.setOption('locale', locale); + this.intl.setLocale(locale); + this.locale = locale; + + return this; + } + setOption(key, value) { key = `${this.optionsPrefix}${dasherize(key)}`; diff --git a/addon/services/fetch.js b/addon/services/fetch.js index 79fbcb6..0e0ee84 100644 --- a/addon/services/fetch.js +++ b/addon/services/fetch.js @@ -360,6 +360,7 @@ export default class FetchService extends Service { const pathKeyVersion = new Date().toISOString(); const request = () => { + delete options.fromCache; return this.get(path, query, options).then((response) => { // cache the response this.localCache.set(pathKey, response); diff --git a/addon/utils/register-component.js b/addon/utils/register-component.js new file mode 100644 index 0000000..1c6df1c --- /dev/null +++ b/addon/utils/register-component.js @@ -0,0 +1,8 @@ +import { dasherize } from '@ember/string'; + +export default function registerComponent(owner, componentClass, options = {}) { + const registrationName = options && options.as ? `component:${options.as}` : `component:${dasherize(componentClass.name).replace('-component', '')}`; + if (!owner.hasRegistration(registrationName)) { + owner.register(registrationName, componentClass); + } +} diff --git a/addon/utils/register-helper.js b/addon/utils/register-helper.js new file mode 100644 index 0000000..396a64f --- /dev/null +++ b/addon/utils/register-helper.js @@ -0,0 +1,8 @@ +import { dasherize } from '@ember/string'; + +export default function registerHelper(owner, name, helperFn) { + const registrationName = `helper:${dasherize(name)}`; + if (!owner.hasRegistration(registrationName)) { + owner.register(registrationName, helperFn); + } +} diff --git a/app/utils/register-component.js b/app/utils/register-component.js new file mode 100644 index 0000000..af6623f --- /dev/null +++ b/app/utils/register-component.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-core/utils/register-component'; diff --git a/app/utils/register-helper.js b/app/utils/register-helper.js new file mode 100644 index 0000000..2f651db --- /dev/null +++ b/app/utils/register-helper.js @@ -0,0 +1 @@ +export { default } from '@fleetbase/ember-core/utils/register-helper'; diff --git a/package.json b/package.json index 7553690..923dbed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fleetbase/ember-core", - "version": "0.2.18", + "version": "0.2.19", "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.", "keywords": [ "fleetbase-core", diff --git a/tests/unit/utils/register-component-test.js b/tests/unit/utils/register-component-test.js new file mode 100644 index 0000000..ec3f9f5 --- /dev/null +++ b/tests/unit/utils/register-component-test.js @@ -0,0 +1,10 @@ +import registerComponent from 'dummy/utils/register-component'; +import { module, test } from 'qunit'; + +module('Unit | Utility | register-component', function () { + // TODO: Replace this with your real tests. + test('it works', function (assert) { + let result = registerComponent(); + assert.ok(result); + }); +}); diff --git a/tests/unit/utils/register-helper-test.js b/tests/unit/utils/register-helper-test.js new file mode 100644 index 0000000..57b49c1 --- /dev/null +++ b/tests/unit/utils/register-helper-test.js @@ -0,0 +1,10 @@ +import registerHelper from 'dummy/utils/register-helper'; +import { module, test } from 'qunit'; + +module('Unit | Utility | register-helper', function () { + // TODO: Replace this with your real tests. + test('it works', function (assert) { + let result = registerHelper(); + assert.ok(result); + }); +}); From 55b1a19bf17e2b1f1b7ab40a1aa53f1224257771 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" <me@ron.dev> Date: Wed, 25 Sep 2024 14:55:56 +0800 Subject: [PATCH 2/5] ran linter --- addon/services/current-user.js | 44 +++++++++-------- addon/services/fetch.js | 86 +++++++++++++++++++--------------- addon/services/universe.js | 4 ++ 3 files changed, 73 insertions(+), 61 deletions(-) diff --git a/addon/services/current-user.js b/addon/services/current-user.js index 804c02b..b0b3d58 100644 --- a/addon/services/current-user.js +++ b/addon/services/current-user.js @@ -33,31 +33,31 @@ export default class CurrentUserService extends Service.extend(Evented) { @alias('user.company_uuid') companyId; @alias('user.company_name') companyName; - @computed('id') get optionsPrefix() { + @computed('id') get optionsPrefix () { return `${this.id}:`; } - get latitude() { + get latitude () { return this.whois('latitude'); } - get longitude() { + get longitude () { return this.whois('longitude'); } - get currency() { + get currency () { return this.whois('currency.code'); } - get city() { + get city () { return this.whois('city'); } - get country() { + get country () { return this.whois('country_code'); } - async load() { + async load () { if (this.session.isAuthenticated) { const user = await this.store.findRecord('user', 'me'); this.set('user', user); @@ -75,7 +75,7 @@ export default class CurrentUserService extends Service.extend(Evented) { return null; } - async promiseUser(options = {}) { + async promiseUser (options = {}) { const NoUserAuthenticatedError = new Error('Failed to authenticate user.'); if (!this.session.isAuthenticated) { throw NoUserAuthenticatedError; @@ -88,8 +88,6 @@ export default class CurrentUserService extends Service.extend(Evented) { this.set('user', user); this.trigger('user.loaded', user); - console.log(user); - // Set permissions this.permissions = this.getUserPermissions(user); @@ -121,13 +119,13 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - async loadPreferences() { + async loadPreferences () { await this.loadLocale(); await this.loadWhois(); await this.loadOrganizations(); } - async loadLocale() { + async loadLocale () { try { const { locale } = await this.fetch.get('users/locale'); this.setLocale(locale); @@ -138,7 +136,7 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - async loadOrganizations() { + async loadOrganizations () { try { const organizations = await this.fetch.get('auth/organizations', {}, { normalizeToEmberData: true, normalizeModelType: 'company' }); this.setOption('organizations', organizations); @@ -150,7 +148,7 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - async loadWhois() { + async loadWhois () { this.fetch.shouldResetCache(); try { @@ -171,12 +169,12 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - getCompany() { + getCompany () { this.company = this.store.peekRecord('company', this.user.company_uuid); return this.company; } - getUserPermissions(user) { + getUserPermissions (user) { const permissions = []; // get direct applied permissions @@ -213,11 +211,11 @@ export default class CurrentUserService extends Service.extend(Evented) { return permissions; } - whois(key) { + whois (key) { return this.getWhoisProperty(key); } - setLocale(locale) { + setLocale (locale) { this.setOption('locale', locale); this.intl.setLocale(locale); this.locale = locale; @@ -225,7 +223,7 @@ export default class CurrentUserService extends Service.extend(Evented) { return this; } - setOption(key, value) { + setOption (key, value) { key = `${this.optionsPrefix}${dasherize(key)}`; this.options.set(key, value); @@ -233,14 +231,14 @@ export default class CurrentUserService extends Service.extend(Evented) { return this; } - getOption(key, defaultValue = null) { + getOption (key, defaultValue = null) { key = `${this.optionsPrefix}${dasherize(key)}`; const value = this.options.get(key); return value !== undefined ? value : defaultValue; } - getWhoisProperty(prop) { + getWhoisProperty (prop) { const whois = this.getOption('whois'); if (!whois || typeof whois !== 'object') { @@ -250,11 +248,11 @@ export default class CurrentUserService extends Service.extend(Evented) { return get(whois, prop); } - hasOption(key) { + hasOption (key) { return this.getOption(key) !== undefined; } - filledOption(key) { + filledOption (key) { return !isBlank(this.getOption(key)); } } diff --git a/addon/services/fetch.js b/addon/services/fetch.js index 0e0ee84..572176b 100644 --- a/addon/services/fetch.js +++ b/addon/services/fetch.js @@ -27,7 +27,7 @@ export default class FetchService extends Service { * Creates an instance of FetchService. * @memberof FetchService */ - constructor() { + constructor () { super(...arguments); this.headers = this.getHeaders(); @@ -61,7 +61,7 @@ export default class FetchService extends Service { * * @return {Object} */ - getHeaders() { + getHeaders () { const headers = {}; const isAuthenticated = this.session.isAuthenticated; const userId = this.session.data.authenticated.user; @@ -92,7 +92,7 @@ export default class FetchService extends Service { * @return {FetchService} * @memberof FetchService */ - refreshHeaders() { + refreshHeaders () { this.headers = this.getHeaders(); return this; @@ -105,7 +105,7 @@ export default class FetchService extends Service { * @return {FetchService} * @memberof FetchService */ - setNamespace(namespace) { + setNamespace (namespace) { this.namespace = namespace; return this; @@ -118,7 +118,7 @@ export default class FetchService extends Service { * @return {FetchService} * @memberof FetchService */ - setHost(host) { + setHost (host) { this.host = host; return this; @@ -174,7 +174,7 @@ export default class FetchService extends Service { * * @return {Model} An ember model */ - normalizeModel(payload, modelType = null) { + normalizeModel (payload, modelType = null) { if (modelType === null) { const modelTypeKeys = Object.keys(payload); modelType = modelTypeKeys.length ? modelTypeKeys.firstObject : false; @@ -187,11 +187,11 @@ export default class FetchService extends Service { const type = dasherize(singularize(modelType)); if (isArray(payload)) { - return payload.map((instance) => this.store.push(this.store.normalize(type, instance))); + return payload.map(instance => this.store.push(this.store.normalize(type, instance))); } if (isArray(payload[modelType])) { - return payload[modelType].map((instance) => this.store.push(this.store.normalize(type, instance))); + return payload[modelType].map(instance => this.store.push(this.store.normalize(type, instance))); } if (!isBlank(payload) && isBlank(payload[modelType])) { @@ -209,7 +209,7 @@ export default class FetchService extends Service { * * @return {Model} An ember model */ - jsonToModel(attributes = {}, modelType) { + jsonToModel (attributes = {}, modelType) { if (typeof attributes === 'string') { attributes = JSON.parse(attributes); } @@ -228,7 +228,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - async parseJSON(response) { + async parseJSON (response) { try { const compressedHeader = await response.headers.get('x-compressed-json'); let json; @@ -265,7 +265,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - request(path, method = 'GET', data = {}, options = {}) { + request (path, method = 'GET', data = {}, options = {}) { const headers = Object.assign(this.getHeaders(), options.headers ?? {}); const host = options.host ?? this.host; const namespace = options.namespace ?? this.namespace; @@ -280,7 +280,7 @@ export default class FetchService extends Service { ...data, }) .then(this.parseJSON) - .then((response) => { + .then(response => { if (response.ok) { if (options.normalizeToEmberData) { const normalized = this.normalizeModel(response.json, options.normalizeModelType); @@ -334,7 +334,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - get(path, query = {}, options = {}) { + get (path, query = {}, options = {}) { // handle if want to request from cache if (options.fromCache === true) { return this.cachedGet(...arguments); @@ -355,13 +355,13 @@ export default class FetchService extends Service { * * @return {Promise} */ - cachedGet(path, query = {}, options = {}) { + cachedGet (path, query = {}, options = {}) { const pathKey = dasherize(path); const pathKeyVersion = new Date().toISOString(); const request = () => { delete options.fromCache; - return this.get(path, query, options).then((response) => { + return this.get(path, query, options).then(response => { // cache the response this.localCache.set(pathKey, response); this.localCache.set(`${pathKey}-version`, pathKeyVersion); @@ -373,7 +373,7 @@ export default class FetchService extends Service { // check to see if in storage already if (this.localCache.get(pathKey)) { - return new Promise((resolve) => { + return new Promise(resolve => { // get cached data const data = this.localCache.get(pathKey); @@ -409,14 +409,24 @@ export default class FetchService extends Service { return request(); } - flushRequestCache(path) { + /** + * Flushes the local cache for a specific path by setting its value and version to undefined. + * + * @param {string} path - The path for which the cache should be flushed. + */ + flushRequestCache (path) { const pathKey = dasherize(path); this.localCache.set(pathKey, undefined); this.localCache.set(`${pathKey}-version`, undefined); } - shouldResetCache() { + /** + * Determines whether the cache should be reset by comparing the current version + * of the console with the cached version. If they differ, the cache is cleared + * and the new version is saved. + */ + shouldResetCache () { const consoleVersion = this.localCache.get('console-version'); if (!consoleVersion || consoleVersion !== config.APP.version) { @@ -434,7 +444,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - post(path, data = {}, options = {}) { + post (path, data = {}, options = {}) { return this.request(path, 'POST', { body: JSON.stringify(data) }, options); } @@ -447,7 +457,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - put(path, data = {}, options = {}) { + put (path, data = {}, options = {}) { return this.request(path, 'PUT', { body: JSON.stringify(data) }, options); } @@ -460,7 +470,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - delete(path, data = {}, options = {}) { + delete (path, data = {}, options = {}) { return this.request(path, 'DELETE', { body: JSON.stringify(data) }, options); } @@ -472,7 +482,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - patch(path, data = {}, options = {}) { + patch (path, data = {}, options = {}) { return this.request(path, 'PATCH', { body: JSON.stringify(data) }, options); } @@ -485,9 +495,9 @@ export default class FetchService extends Service { * * @return {Promise} */ - upload(path, files = [], options = {}) { + upload (path, files = [], options = {}) { const body = new FormData(); - files.forEach((file) => { + files.forEach(file => { body.append('file', file); }); return this.request(path, 'POST', { body }, options); @@ -502,12 +512,12 @@ export default class FetchService extends Service { * @param {String} profile * @param {String} version */ - routing(coordinates, query = {}, options = {}) { + routing (coordinates, query = {}, options = {}) { let service = options?.service ?? 'trip'; let profile = options?.profile ?? 'driving'; let version = options?.version ?? 'v1'; let host = options?.host ?? `https://${options?.subdomain ?? 'routing'}.fleetbase.io`; - let route = coordinates.map((coords) => coords.join(',')).join(';'); + let route = coordinates.map(coords => coords.join(',')).join(';'); let params = !isEmptyObject(query) ? new URLSearchParams(query).toString() : ''; let path = `${host}/${service}/${version}/${profile}/${route}`; let url = `${path}${params ? '?' + params : ''}`; @@ -555,8 +565,8 @@ export default class FetchService extends Service { withCredentials: true, headers, }) - .then((response) => response.json()) - .catch((error) => { + .then(response => response.json()) + .catch(error => { this.notifications.serverError(error, 'File upload failed.'); if (typeof errorCallback === 'function') { @@ -598,7 +608,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - download(path, query = {}, options = {}) { + download (path, query = {}, options = {}) { const headers = Object.assign(this.getHeaders(), options.headers ?? {}); const method = options.method ?? 'GET'; const credentials = options.credentials ?? this.credentials; @@ -619,7 +629,7 @@ export default class FetchService extends Service { return new Promise((resolve, reject) => { return fetch(`${baseUrl}/${path}${params}`, fetchOptions) - .then((response) => { + .then(response => { options.fileName = this.getFilenameFromResponse(response, options.fileName); options.mimeType = this.getMimeTypeFromResponse(response, options.mimeType); @@ -629,15 +639,15 @@ export default class FetchService extends Service { return response; }) - .then((response) => response.blob()) - .then((blob) => resolve(download(blob, options.fileName, options.mimeType))) - .catch((error) => { + .then(response => response.blob()) + .then(blob => resolve(download(blob, options.fileName, options.mimeType))) + .catch(error => { reject(error); }); }); } - getFilenameFromResponse(response, defaultFilename = null) { + getFilenameFromResponse (response, defaultFilename = null) { const contentDisposition = response.headers.get('content-disposition'); let fileName = defaultFilename; @@ -655,7 +665,7 @@ export default class FetchService extends Service { return fileName; } - getMimeTypeFromResponse(response, defaultMimeType = null) { + getMimeTypeFromResponse (response, defaultMimeType = null) { const contentType = response.headers.get('content-type'); let mimeType = defaultMimeType; @@ -670,10 +680,10 @@ export default class FetchService extends Service { return mimeType; } - fetchOrderConfigurations(params = {}) { + fetchOrderConfigurations (params = {}) { return new Promise((resolve, reject) => { this.request('fleet-ops/order-configs/get-installed', params) - .then((configs) => { + .then(configs => { const serialized = []; for (let i = 0; i < configs.length; i++) { @@ -686,7 +696,7 @@ export default class FetchService extends Service { resolve(serialized); }) - .catch((error) => { + .catch(error => { reject(error); }); }); diff --git a/addon/services/universe.js b/addon/services/universe.js index ca3cb8f..e9f71a2 100644 --- a/addon/services/universe.js +++ b/addon/services/universe.js @@ -777,9 +777,11 @@ export default class UniverseService extends Service.extend(Evented) { */ registerMenuPanel(registryName, title, items = [], options = {}) { const internalRegistryName = this.createInternalRegistryName(registryName); + const intl = this._getOption(options, 'intl', null); const open = this._getOption(options, 'open', true); const slug = this._getOption(options, 'slug', dasherize(title)); const menuPanel = { + intl, title, open, items: items.map(({ title, route, ...options }) => { @@ -1321,6 +1323,7 @@ export default class UniverseService extends Service.extend(Evented) { * @returns {Object} A new menu item object */ _createMenuItem(title, route, options = {}) { + const intl = this._getOption(options, 'intl', null); const priority = this._getOption(options, 'priority', 9); const icon = this._getOption(options, 'icon', 'circle-dot'); const items = this._getOption(options, 'items'); @@ -1360,6 +1363,7 @@ export default class UniverseService extends Service.extend(Evented) { // @todo: create menu item class const menuItem = { id, + intl, title, text: title, route, From 4c94235e9aeb2de0b9ede5df9a35a836c07e89b0 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" <me@ron.dev> Date: Wed, 25 Sep 2024 15:49:28 +0800 Subject: [PATCH 3/5] fixed cachedGet on fetch method using resolve in cache promise --- addon/services/current-user.js | 42 +++++++++--------- addon/services/fetch.js | 78 +++++++++++++++++----------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/addon/services/current-user.js b/addon/services/current-user.js index b0b3d58..0a36343 100644 --- a/addon/services/current-user.js +++ b/addon/services/current-user.js @@ -33,31 +33,31 @@ export default class CurrentUserService extends Service.extend(Evented) { @alias('user.company_uuid') companyId; @alias('user.company_name') companyName; - @computed('id') get optionsPrefix () { + @computed('id') get optionsPrefix() { return `${this.id}:`; } - get latitude () { + get latitude() { return this.whois('latitude'); } - get longitude () { + get longitude() { return this.whois('longitude'); } - get currency () { + get currency() { return this.whois('currency.code'); } - get city () { + get city() { return this.whois('city'); } - get country () { + get country() { return this.whois('country_code'); } - async load () { + async load() { if (this.session.isAuthenticated) { const user = await this.store.findRecord('user', 'me'); this.set('user', user); @@ -75,7 +75,7 @@ export default class CurrentUserService extends Service.extend(Evented) { return null; } - async promiseUser (options = {}) { + async promiseUser(options = {}) { const NoUserAuthenticatedError = new Error('Failed to authenticate user.'); if (!this.session.isAuthenticated) { throw NoUserAuthenticatedError; @@ -119,13 +119,13 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - async loadPreferences () { + async loadPreferences() { await this.loadLocale(); await this.loadWhois(); await this.loadOrganizations(); } - async loadLocale () { + async loadLocale() { try { const { locale } = await this.fetch.get('users/locale'); this.setLocale(locale); @@ -136,7 +136,7 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - async loadOrganizations () { + async loadOrganizations() { try { const organizations = await this.fetch.get('auth/organizations', {}, { normalizeToEmberData: true, normalizeModelType: 'company' }); this.setOption('organizations', organizations); @@ -148,7 +148,7 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - async loadWhois () { + async loadWhois() { this.fetch.shouldResetCache(); try { @@ -169,12 +169,12 @@ export default class CurrentUserService extends Service.extend(Evented) { } } - getCompany () { + getCompany() { this.company = this.store.peekRecord('company', this.user.company_uuid); return this.company; } - getUserPermissions (user) { + getUserPermissions(user) { const permissions = []; // get direct applied permissions @@ -211,11 +211,11 @@ export default class CurrentUserService extends Service.extend(Evented) { return permissions; } - whois (key) { + whois(key) { return this.getWhoisProperty(key); } - setLocale (locale) { + setLocale(locale) { this.setOption('locale', locale); this.intl.setLocale(locale); this.locale = locale; @@ -223,7 +223,7 @@ export default class CurrentUserService extends Service.extend(Evented) { return this; } - setOption (key, value) { + setOption(key, value) { key = `${this.optionsPrefix}${dasherize(key)}`; this.options.set(key, value); @@ -231,14 +231,14 @@ export default class CurrentUserService extends Service.extend(Evented) { return this; } - getOption (key, defaultValue = null) { + getOption(key, defaultValue = null) { key = `${this.optionsPrefix}${dasherize(key)}`; const value = this.options.get(key); return value !== undefined ? value : defaultValue; } - getWhoisProperty (prop) { + getWhoisProperty(prop) { const whois = this.getOption('whois'); if (!whois || typeof whois !== 'object') { @@ -248,11 +248,11 @@ export default class CurrentUserService extends Service.extend(Evented) { return get(whois, prop); } - hasOption (key) { + hasOption(key) { return this.getOption(key) !== undefined; } - filledOption (key) { + filledOption(key) { return !isBlank(this.getOption(key)); } } diff --git a/addon/services/fetch.js b/addon/services/fetch.js index 572176b..2024343 100644 --- a/addon/services/fetch.js +++ b/addon/services/fetch.js @@ -27,7 +27,7 @@ export default class FetchService extends Service { * Creates an instance of FetchService. * @memberof FetchService */ - constructor () { + constructor() { super(...arguments); this.headers = this.getHeaders(); @@ -61,7 +61,7 @@ export default class FetchService extends Service { * * @return {Object} */ - getHeaders () { + getHeaders() { const headers = {}; const isAuthenticated = this.session.isAuthenticated; const userId = this.session.data.authenticated.user; @@ -92,7 +92,7 @@ export default class FetchService extends Service { * @return {FetchService} * @memberof FetchService */ - refreshHeaders () { + refreshHeaders() { this.headers = this.getHeaders(); return this; @@ -105,7 +105,7 @@ export default class FetchService extends Service { * @return {FetchService} * @memberof FetchService */ - setNamespace (namespace) { + setNamespace(namespace) { this.namespace = namespace; return this; @@ -118,7 +118,7 @@ export default class FetchService extends Service { * @return {FetchService} * @memberof FetchService */ - setHost (host) { + setHost(host) { this.host = host; return this; @@ -174,7 +174,7 @@ export default class FetchService extends Service { * * @return {Model} An ember model */ - normalizeModel (payload, modelType = null) { + normalizeModel(payload, modelType = null) { if (modelType === null) { const modelTypeKeys = Object.keys(payload); modelType = modelTypeKeys.length ? modelTypeKeys.firstObject : false; @@ -187,11 +187,11 @@ export default class FetchService extends Service { const type = dasherize(singularize(modelType)); if (isArray(payload)) { - return payload.map(instance => this.store.push(this.store.normalize(type, instance))); + return payload.map((instance) => this.store.push(this.store.normalize(type, instance))); } if (isArray(payload[modelType])) { - return payload[modelType].map(instance => this.store.push(this.store.normalize(type, instance))); + return payload[modelType].map((instance) => this.store.push(this.store.normalize(type, instance))); } if (!isBlank(payload) && isBlank(payload[modelType])) { @@ -209,7 +209,7 @@ export default class FetchService extends Service { * * @return {Model} An ember model */ - jsonToModel (attributes = {}, modelType) { + jsonToModel(attributes = {}, modelType) { if (typeof attributes === 'string') { attributes = JSON.parse(attributes); } @@ -228,7 +228,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - async parseJSON (response) { + async parseJSON(response) { try { const compressedHeader = await response.headers.get('x-compressed-json'); let json; @@ -265,7 +265,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - request (path, method = 'GET', data = {}, options = {}) { + request(path, method = 'GET', data = {}, options = {}) { const headers = Object.assign(this.getHeaders(), options.headers ?? {}); const host = options.host ?? this.host; const namespace = options.namespace ?? this.namespace; @@ -280,7 +280,7 @@ export default class FetchService extends Service { ...data, }) .then(this.parseJSON) - .then(response => { + .then((response) => { if (response.ok) { if (options.normalizeToEmberData) { const normalized = this.normalizeModel(response.json, options.normalizeModelType); @@ -334,7 +334,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - get (path, query = {}, options = {}) { + get(path, query = {}, options = {}) { // handle if want to request from cache if (options.fromCache === true) { return this.cachedGet(...arguments); @@ -355,13 +355,13 @@ export default class FetchService extends Service { * * @return {Promise} */ - cachedGet (path, query = {}, options = {}) { + cachedGet(path, query = {}, options = {}) { const pathKey = dasherize(path); const pathKeyVersion = new Date().toISOString(); const request = () => { delete options.fromCache; - return this.get(path, query, options).then(response => { + return this.get(path, query, options).then((response) => { // cache the response this.localCache.set(pathKey, response); this.localCache.set(`${pathKey}-version`, pathKeyVersion); @@ -373,7 +373,7 @@ export default class FetchService extends Service { // check to see if in storage already if (this.localCache.get(pathKey)) { - return new Promise(resolve => { + return new Promise((resolve) => { // get cached data const data = this.localCache.get(pathKey); @@ -393,7 +393,7 @@ export default class FetchService extends Service { // if the version is older than 3 days clear it if (!version || shouldExpire || options.clearData === true) { this.flushRequestCache(path); - return request(); + return request().then(resolve); } if (options.normalizeToEmberData) { @@ -414,7 +414,7 @@ export default class FetchService extends Service { * * @param {string} path - The path for which the cache should be flushed. */ - flushRequestCache (path) { + flushRequestCache(path) { const pathKey = dasherize(path); this.localCache.set(pathKey, undefined); @@ -426,7 +426,7 @@ export default class FetchService extends Service { * of the console with the cached version. If they differ, the cache is cleared * and the new version is saved. */ - shouldResetCache () { + shouldResetCache() { const consoleVersion = this.localCache.get('console-version'); if (!consoleVersion || consoleVersion !== config.APP.version) { @@ -444,7 +444,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - post (path, data = {}, options = {}) { + post(path, data = {}, options = {}) { return this.request(path, 'POST', { body: JSON.stringify(data) }, options); } @@ -457,7 +457,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - put (path, data = {}, options = {}) { + put(path, data = {}, options = {}) { return this.request(path, 'PUT', { body: JSON.stringify(data) }, options); } @@ -470,7 +470,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - delete (path, data = {}, options = {}) { + delete(path, data = {}, options = {}) { return this.request(path, 'DELETE', { body: JSON.stringify(data) }, options); } @@ -482,7 +482,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - patch (path, data = {}, options = {}) { + patch(path, data = {}, options = {}) { return this.request(path, 'PATCH', { body: JSON.stringify(data) }, options); } @@ -495,9 +495,9 @@ export default class FetchService extends Service { * * @return {Promise} */ - upload (path, files = [], options = {}) { + upload(path, files = [], options = {}) { const body = new FormData(); - files.forEach(file => { + files.forEach((file) => { body.append('file', file); }); return this.request(path, 'POST', { body }, options); @@ -512,12 +512,12 @@ export default class FetchService extends Service { * @param {String} profile * @param {String} version */ - routing (coordinates, query = {}, options = {}) { + routing(coordinates, query = {}, options = {}) { let service = options?.service ?? 'trip'; let profile = options?.profile ?? 'driving'; let version = options?.version ?? 'v1'; let host = options?.host ?? `https://${options?.subdomain ?? 'routing'}.fleetbase.io`; - let route = coordinates.map(coords => coords.join(',')).join(';'); + let route = coordinates.map((coords) => coords.join(',')).join(';'); let params = !isEmptyObject(query) ? new URLSearchParams(query).toString() : ''; let path = `${host}/${service}/${version}/${profile}/${route}`; let url = `${path}${params ? '?' + params : ''}`; @@ -565,8 +565,8 @@ export default class FetchService extends Service { withCredentials: true, headers, }) - .then(response => response.json()) - .catch(error => { + .then((response) => response.json()) + .catch((error) => { this.notifications.serverError(error, 'File upload failed.'); if (typeof errorCallback === 'function') { @@ -608,7 +608,7 @@ export default class FetchService extends Service { * * @return {Promise} */ - download (path, query = {}, options = {}) { + download(path, query = {}, options = {}) { const headers = Object.assign(this.getHeaders(), options.headers ?? {}); const method = options.method ?? 'GET'; const credentials = options.credentials ?? this.credentials; @@ -629,7 +629,7 @@ export default class FetchService extends Service { return new Promise((resolve, reject) => { return fetch(`${baseUrl}/${path}${params}`, fetchOptions) - .then(response => { + .then((response) => { options.fileName = this.getFilenameFromResponse(response, options.fileName); options.mimeType = this.getMimeTypeFromResponse(response, options.mimeType); @@ -639,15 +639,15 @@ export default class FetchService extends Service { return response; }) - .then(response => response.blob()) - .then(blob => resolve(download(blob, options.fileName, options.mimeType))) - .catch(error => { + .then((response) => response.blob()) + .then((blob) => resolve(download(blob, options.fileName, options.mimeType))) + .catch((error) => { reject(error); }); }); } - getFilenameFromResponse (response, defaultFilename = null) { + getFilenameFromResponse(response, defaultFilename = null) { const contentDisposition = response.headers.get('content-disposition'); let fileName = defaultFilename; @@ -665,7 +665,7 @@ export default class FetchService extends Service { return fileName; } - getMimeTypeFromResponse (response, defaultMimeType = null) { + getMimeTypeFromResponse(response, defaultMimeType = null) { const contentType = response.headers.get('content-type'); let mimeType = defaultMimeType; @@ -680,10 +680,10 @@ export default class FetchService extends Service { return mimeType; } - fetchOrderConfigurations (params = {}) { + fetchOrderConfigurations(params = {}) { return new Promise((resolve, reject) => { this.request('fleet-ops/order-configs/get-installed', params) - .then(configs => { + .then((configs) => { const serialized = []; for (let i = 0; i < configs.length; i++) { @@ -696,7 +696,7 @@ export default class FetchService extends Service { resolve(serialized); }) - .catch(error => { + .catch((error) => { reject(error); }); }); From 2c61c2d68383c6dd4d87f8b8c4ae91da2a1a4315 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" <me@ron.dev> Date: Sat, 28 Sep 2024 22:20:14 +0800 Subject: [PATCH 4/5] added a post boot callback for engine boots --- addon/services/universe.js | 71 ++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/addon/services/universe.js b/addon/services/universe.js index e9f71a2..14606db 100644 --- a/addon/services/universe.js +++ b/addon/services/universe.js @@ -42,6 +42,7 @@ export default class UniverseService extends Service.extend(Evented) { widgets: A([]), }; @tracked hooks = {}; + @tracked bootCallbacks = A([]); /** * Computed property that returns all administrative menu items. @@ -1734,7 +1735,7 @@ export default class UniverseService extends Service.extend(Evented) { * @param {ApplicationInstance|null} owner - The Ember ApplicationInstance that owns the engines. * @return {void} */ - bootEngines(owner = null) { + async bootEngines(owner = null) { const booted = []; const pending = []; const additionalCoreExtensions = config.APP.extensions ?? []; @@ -1748,7 +1749,7 @@ export default class UniverseService extends Service.extend(Evented) { this.applicationInstance = owner; const tryBootEngine = (extension) => { - this.loadEngine(extension.name).then((engineInstance) => { + return this.loadEngine(extension.name).then((engineInstance) => { if (engineInstance.base && engineInstance.base.setupExtension) { if (this.bootedExtensions.includes(extension.name)) { return; @@ -1803,11 +1804,13 @@ export default class UniverseService extends Service.extend(Evented) { pending.push(...stillPending); }; - return loadInstalledExtensions(additionalCoreExtensions).then((extensions) => { - extensions.forEach((extension) => { - tryBootEngine(extension); - }); + return loadInstalledExtensions(additionalCoreExtensions).then(async (extensions) => { + for (let i = 0; i < extensions.length; i++) { + const extension = extensions[i]; + await tryBootEngine(extension); + } + this.runBootCallbacks(owner); this.enginesBooted = true; }); } @@ -1838,7 +1841,7 @@ export default class UniverseService extends Service.extend(Evented) { this.applicationInstance = owner; const tryBootEngine = (extension) => { - this.loadEngine(extension.name).then((engineInstance) => { + return this.loadEngine(extension.name).then((engineInstance) => { if (engineInstance.base && engineInstance.base.setupExtension) { const engineDependencies = getWithDefault(engineInstance.base, 'engineDependencies', []); @@ -1887,11 +1890,13 @@ export default class UniverseService extends Service.extend(Evented) { pending.push(...stillPending); }; - return loadExtensions().then((extensions) => { - extensions.forEach((extension) => { - tryBootEngine(extension); - }); + return loadExtensions().then(async (extensions) => { + for (let i = 0; i < extensions.length; i++) { + const extension = extensions[i]; + await tryBootEngine(extension); + } + this.runBootCallbacks(owner); this.enginesBooted = true; }); } @@ -1907,6 +1912,50 @@ export default class UniverseService extends Service.extend(Evented) { return this.bootedExtensions.includes(name); } + /** + * Registers a callback function to be executed after the engine boot process completes. + * + * This method ensures that the `bootCallbacks` array is initialized. It then adds the provided + * callback to this array. The callbacks registered will be invoked in sequence after the engine + * has finished booting, using the `runBootCallbacks` method. + * + * @param {Function} callback - The function to execute after the engine boots. + * The callback should accept two arguments: + * - `{Object} universe` - The universe context or environment. + * - `{Object} appInstance` - The application instance. + */ + afterBoot(callback) { + if (!isArray(this.bootCallbacks)) { + this.bootCallbacks = []; + } + + this.bootCallbacks.pushObject(callback); + } + + /** + * Executes all registered engine boot callbacks in the order they were added. + * + * This method iterates over the `bootCallbacks` array and calls each callback function, + * passing in the `universe` and `appInstance` parameters. After all callbacks have been + * executed, it optionally calls a completion function `onComplete`. + * + * @param {Object} appInstance - The application instance to pass to each callback. + * @param {Function} [onComplete] - Optional. A function to call after all boot callbacks have been executed. + * It does not receive any arguments. + */ + runBootCallbacks(appInstance, onComplete = null) { + for (let i = 0; i < this.bootCallbacks.length; i++) { + const callback = this.bootCallbacks[i]; + if (typeof callback === 'function') { + callback(this, appInstance); + } + } + + if (typeof onComplete === 'function') { + onComplete(); + } + } + /** * Alias for intl service `t` * From f0bedd3b5f8024ed4b317ba57322a20de3ce4c02 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" <me@ron.dev> Date: Tue, 1 Oct 2024 10:49:48 +0800 Subject: [PATCH 5/5] improvements to current user service, universe service, and url search params service, and fixed child service injection for engineService decorator --- addon/services/current-user.js | 2 + addon/services/universe.js | 61 +++++++- addon/services/url-search-params.js | 224 ++++++++++++++++++++++----- addon/utils/inject-engine-service.js | 9 +- 4 files changed, 257 insertions(+), 39 deletions(-) diff --git a/addon/services/current-user.js b/addon/services/current-user.js index 0a36343..defc226 100644 --- a/addon/services/current-user.js +++ b/addon/services/current-user.js @@ -32,6 +32,8 @@ export default class CurrentUserService extends Service.extend(Evented) { @alias('user.is_admin') isAdmin; @alias('user.company_uuid') companyId; @alias('user.company_name') companyName; + @alias('user.role_name') roleName; + @alias('user.role') role; @computed('id') get optionsPrefix() { return `${this.id}:`; diff --git a/addon/services/universe.js b/addon/services/universe.js index 14606db..f45443e 100644 --- a/addon/services/universe.js +++ b/addon/services/universe.js @@ -19,6 +19,7 @@ import config from 'ember-get-config'; export default class UniverseService extends Service.extend(Evented) { @service router; @service intl; + @service urlSearchParams; @tracked applicationInstance; @tracked enginesBooted = false; @tracked bootedExtensions = A([]); @@ -136,6 +137,17 @@ export default class UniverseService extends Service.extend(Evented) { return this.router.transitionTo(route, ...args); } + /** + * Sets the application instance. + * + * @param {ApplicationInstance} - The application instance object. + * @return {void} + */ + setApplicationInstance(instance) { + window.Fleetbase = instance; + this.applicationInstance = instance; + } + /** * Retrieves the application instance. * @@ -262,6 +274,43 @@ export default class UniverseService extends Service.extend(Evented) { return this.router.transitionTo(route, slug); } + /** + * Redirects to a virtual route if a corresponding menu item exists based on the current URL slug. + * + * This asynchronous function checks whether a virtual route exists by extracting the slug from the current + * window's pathname and looking up a matching menu item in a specified registry. If a matching menu item + * is found, it initiates a transition to the given route associated with that menu item and returns the + * transition promise. + * + * @async + * + * @param {Object} transition - The current transition object from the router. + * Used to retrieve additional information required for the menu item lookup. + * @param {string} registryName - The name of the registry to search for the menu item. + * This registry should contain menu items mapped by their slugs. + * @param {string} route - The name of the route to transition to if the menu item is found. + * This is typically the route associated with displaying the menu item's content. + * + * @returns {Promise|undefined} - Returns a promise that resolves when the route transition completes + * if a matching menu item is found. If no matching menu item is found, the function returns undefined. + * + */ + async virtualRouteRedirect(transition, registryName, route, options = {}) { + const view = this.getViewFromTransition(transition); + const slug = window.location.pathname.replace('/', ''); + const queryParams = this.urlSearchParams.all(); + const menuItem = await this.lookupMenuItemFromRegistry(registryName, slug, view); + if (menuItem && transition.from === null) { + return this.transitionMenuItem(route, menuItem, { queryParams }).then((transition) => { + if (options && options.restoreQueryParams === true) { + this.urlSearchParams.setParamsToCurrentUrl(queryParams); + } + + return transition; + }); + } + } + /** * @action * Creates a new registry with the given name and options. @@ -1396,6 +1445,14 @@ export default class UniverseService extends Service.extend(Evented) { isLoading, }; + // make the menu item and universe object a default param of the onClick handler + if (typeof onClick === 'function') { + const universe = this; + menuItem.onClick = function () { + return onClick(menuItem, universe); + }; + } + return menuItem; } @@ -1746,7 +1803,7 @@ export default class UniverseService extends Service.extend(Evented) { } // Set application instance - this.applicationInstance = owner; + this.setApplicationInstance(owner); const tryBootEngine = (extension) => { return this.loadEngine(extension.name).then((engineInstance) => { @@ -1838,7 +1895,7 @@ export default class UniverseService extends Service.extend(Evented) { } // Set application instance - this.applicationInstance = owner; + this.setApplicationInstance(owner); const tryBootEngine = (extension) => { return this.loadEngine(extension.name).then((engineInstance) => { diff --git a/addon/services/url-search-params.js b/addon/services/url-search-params.js index f37c751..6beef64 100644 --- a/addon/services/url-search-params.js +++ b/addon/services/url-search-params.js @@ -1,35 +1,35 @@ import Service from '@ember/service'; -import { tracked } from '@glimmer/tracking'; +import { debounce } from '@ember/runloop'; import hasJsonStructure from '../utils/has-json-structure'; +/** + * Service for manipulating URL search parameters. + * + * This service provides methods to get, set, remove, and check URL query parameters. + * It also allows updating the browser's URL without reloading the page. + * + * @extends Service + */ export default class UrlSearchParamsService extends Service { /** - * The active URL params + * Getter for `urlParams` that ensures it's always up-to-date with the current URL. * - * @var {Array} + * @type {URLSearchParams} + * @private */ - @tracked urlParams; - - /** - * Update the URL params - * - * @void - */ - setSearchParams() { - this.urlParams = new URLSearchParams(window.location.search); - - return this; + get urlParams() { + return new URLSearchParams(window.location.search); } /** - * Get a param + * Retrieves the value of a specific query parameter. * - * @param {String} key the url param - * @return mixed + * If the parameter value is a JSON string, it will be parsed into an object or array. + * + * @param {string} key - The name of the query parameter to retrieve. + * @returns {*} The value of the query parameter, parsed from JSON if applicable, or null if not found. */ getParam(key) { - this.setSearchParams(); - let value = this.urlParams.get(key); if (hasJsonStructure(value)) { @@ -40,47 +40,102 @@ export default class UrlSearchParamsService extends Service { } /** - * Get a param + * Sets or updates a query parameter in the URL search parameters. + * + * If the value is an object or array, it will be stringified to JSON. + * + * @param {string} key - The name of the query parameter to set. + * @param {*} value - The value of the query parameter. + * @returns {this} Returns the service instance for chaining. + */ + setParam(key, value) { + if (typeof value === 'object') { + value = JSON.stringify(value); + } else { + value = encodeURIComponent(value); + } + + this.urlParams.set(key, value); + + return this; + } + + /** + * Alias for `getParam`. * - * @param {String} key the url param - * @return mixed + * @param {string} key - The name of the query parameter to retrieve. + * @returns {*} The value of the query parameter. */ get(key) { return this.getParam(key); } /** - * Determines if a queryParam exists + * Sets or updates a query parameter with multiple values. * - * @param {String} key the url param - * @var {Boolean} + * @param {string} key - The name of the query parameter to set. + * @param {Array} values - An array of values for the parameter. + * @returns {this} Returns the service instance for chaining. + */ + setParamArray(key, values) { + this.urlParams.delete(key); + values.forEach((value) => { + this.urlParams.append(key, value); + }); + + return this; + } + + /** + * Retrieves all values of a specific query parameter. + * + * @param {string} key - The name of the query parameter. + * @returns {Array} An array of values for the parameter. + */ + getParamArray(key) { + return this.urlParams.getAll(key); + } + + /** + * Checks if a specific query parameter exists in the URL. + * + * @param {string} key - The name of the query parameter to check. + * @returns {boolean} True if the parameter exists, false otherwise. */ exists(key) { - this.setSearchParams(); + return this.urlParams.has(key); + } + /** + * Checks if a specific query parameter has in the URL. + * + * @param {string} key - The name of the query parameter to check. + * @returns {boolean} True if the parameter exists, false otherwise. + */ + has(key) { return this.urlParams.has(key); } /** - * Remove a queryparam + * Removes a specific query parameter from the URL search parameters. * - * @param {String} key the url param - * @void + * @param {string} key - The name of the query parameter to remove. + * @returns {this} Returns the service instance for chaining. */ remove(key) { - this.setSearchParams(); + this.urlParams.delete(key); - return this.urlParams.delete(key); + return this; } /** - * Returns object of all params + * Retrieves all query parameters as an object. * - * @return {Array} + * Each parameter value is processed by `getParam`, which parses JSON values if applicable. + * + * @returns {Object} An object containing all query parameters and their values. */ all() { - this.setSearchParams(); - const all = {}; for (let key of this.urlParams.keys()) { @@ -89,4 +144,103 @@ export default class UrlSearchParamsService extends Service { return all; } + + /** + * Updates the browser's URL with the current `urlParams` without reloading the page. + * + * @returns {void} + */ + updateUrl() { + const url = new URL(window.location.href); + url.search = this.urlParams.toString(); + window.history.pushState({ path: url.href }, '', url.href); + } + + /** + * Updates the browser's URL with the current `urlParams`, debounced to prevent excessive calls. + * + * @returns {void} + */ + updateUrlDebounced() { + debounce(this, this.updateUrl, 100); + } + + /** + * Clears all query parameters from the URL search parameters. + * + * @returns {this} Returns the service instance for chaining. + */ + clear() { + this.urlParams = new URLSearchParams(); + + return this; + } + + /** + * Returns the full URL as a string with the current `urlParams`. + * + * @returns {string} The full URL with updated query parameters. + */ + getFullUrl() { + const url = new URL(window.location.href); + url.search = this.urlParams.toString(); + + return url.toString(); + } + + /** + * Returns the current path with the updated query parameters. + * + * @returns {string} The path and search portion of the URL. + */ + getPathWithParams() { + return `${window.location.pathname}?${this.urlParams.toString()}`; + } + + /** + * Removes a query parameter from the current URL and updates the browser history. + * + * This method modifies the browser's URL by removing the specified parameter and uses the History API + * to update the URL without reloading the page. + * + * @param {string} paramToRemove - The name of the query parameter to remove from the URL. + * @returns {void} + */ + removeParamFromCurrentUrl(paramToRemove) { + const url = new URL(window.location.href); + url.searchParams.delete(paramToRemove); + window.history.pushState({ path: url.href }, '', url.href); + } + + /** + * Adds or updates a query parameter in the current URL and updates the browser history. + * + * This method modifies the browser's URL by adding or updating the specified parameter and uses the History API + * to update the URL without reloading the page. + * + * @param {string} paramName - The name of the query parameter to add or update. + * @param {string} paramValue - The value of the query parameter. + * @returns {void} + */ + addParamToCurrentUrl(paramName, paramValue) { + const url = new URL(window.location.href); + url.searchParams.set(paramName, paramValue); + window.history.pushState({ path: url.href }, '', url.href); + } + + /** + * Adds or updates a query parameters from a provided object into the current URL and updates the browser history. + * + * This method modifies the browser's URL by adding or updating the specified parameters and uses the History API + * to update the URL without reloading the page. + * + * @param {Object} params - The query parameters to add or update. + * @returns {void} + */ + setParamsToCurrentUrl(params = {}) { + for (let param in params) { + const value = params[param]; + this.addParamToCurrentUrl(param, value); + } + } } diff --git a/addon/utils/inject-engine-service.js b/addon/utils/inject-engine-service.js index cdb7ebb..721e0d5 100644 --- a/addon/utils/inject-engine-service.js +++ b/addon/utils/inject-engine-service.js @@ -1,10 +1,11 @@ import { getOwner } from '@ember/application'; import { isArray } from '@ember/array'; +import Service from '@ember/service'; import isObject from './is-object'; function findService(owner, target, serviceName) { let service = target[serviceName]; - if (!service) { + if (!(service instanceof Service)) { service = owner.lookup(`service:${serviceName}`); } @@ -33,8 +34,12 @@ function automaticServiceResolution(service, target, owner) { } } +function _getOwner(target) { + return window.Fleetbase ?? getOwner(target); +} + export default function injectEngineService(target, engineName, serviceName, options = {}) { - const owner = getOwner(target); + const owner = _getOwner(target); const universe = owner.lookup('service:universe'); const service = universe.getServiceFromEngine(engineName, serviceName); const key = options.key || null;