@fleetbase/ember-core 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,15 +5,17 @@ import { inject as service } from '@ember/service';
5
5
  import { storageFor } from 'ember-local-storage';
6
6
  import { get } from '@ember/object';
7
7
  import { isBlank } from '@ember/utils';
8
+ import { isArray } from '@ember/array';
8
9
  import { dasherize } from '@ember/string';
9
10
  import { pluralize } from 'ember-inflector';
11
+ import { decompress as decompressJson } from 'compress-json';
10
12
  import getUserOptions from '../utils/get-user-options';
11
13
  import config from 'ember-get-config';
12
14
 
13
15
  if (isBlank(config.API.host)) {
14
16
  config.API.host = `${window.location.protocol}//${window.location.hostname}:8000`;
15
17
  }
16
-
18
+ const DEFAULT_ERROR_MESSAGE = 'Oops! Something went wrong. Please try again or contact support if the issue persists.';
17
19
  export default class ApplicationAdapter extends RESTAdapter {
18
20
  /**
19
21
  * Inject the `session` service
@@ -150,25 +152,82 @@ export default class ApplicationAdapter extends RESTAdapter {
150
152
  }
151
153
 
152
154
  /**
153
- * Handles the response from an AJAX request in an Ember application.
154
- *
155
- * @param {number} status - The HTTP status code of the response.
156
- * @param {object} headers - The headers of the response.
157
- * @param {object} payload - The payload of the response.
158
- * @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.
155
+ * Handles the response from an AJAX request.
156
+ * It decompresses the payload if needed, checks for error responses, and returns an AdapterError for error scenarios.
157
+ * For valid responses, the handling is delegated to the superclass's handleResponse method.
159
158
  *
160
- * This method first normalizes the error response and generates a detailed message.
161
- * 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.
162
- * For valid responses, it delegates the handling to the superclass's `handleResponse` method.
159
+ * @param {number} status - HTTP status code of the response.
160
+ * @param {object} headers - Response headers.
161
+ * @param {object} payload - Response payload.
162
+ * @param {object} requestData - Original request data.
163
+ * @return {Object | AdapterError} - The response object or an AdapterError in case of errors.
163
164
  */
164
- handleResponse(status, headers, payload) {
165
- let errors = this.normalizeErrorResponse(status, headers, payload);
166
- let detailedMessage = this.generatedDetailedMessage(...arguments);
165
+ handleResponse(status, headers, payload, requestData) {
166
+ const decompressedPayload = this.decompressPayload(payload, headers);
167
+ if (this.isErrorResponse(status, decompressedPayload)) {
168
+ const errors = this.getResponseErrors(decompressedPayload);
169
+ const errorMessage = this.getErrorMessage(errors);
170
+ return new AdapterError(errors, errorMessage);
171
+ }
167
172
 
168
- if (this.isInvalid(status, headers, payload)) {
169
- return new AdapterError(errors, detailedMessage);
173
+ return super.handleResponse(status, headers, decompressedPayload, requestData);
174
+ }
175
+
176
+ /**
177
+ * Decompresses the response payload if it's marked as compressed in the response headers.
178
+ *
179
+ * This method checks the response headers for a specific 'x-compressed-json' flag.
180
+ * If this flag is set, indicating that the response payload is compressed, the method
181
+ * decompresses the payload. The decompressed payload is then parsed as JSON and returned.
182
+ * If the payload is not compressed, it is returned as is.
183
+ *
184
+ * @param {object} payload - The original payload of the response.
185
+ * @param {object} headers - The headers of the response, used to check if the payload is compressed.
186
+ * @return {object} The decompressed payload if it was compressed, or the original payload otherwise.
187
+ */
188
+ decompressPayload(payload, headers) {
189
+ // Check if the response is compressed
190
+ if (headers['x-compressed-json'] === '1' || headers['x-compressed-json'] === 1) {
191
+ // Decompress the payload
192
+ const decompressedPayload = decompressJson(payload);
193
+ // Replace payload with decompressed json payload
194
+ payload = JSON.parse(decompressedPayload);
170
195
  }
171
196
 
172
- return super.handleResponse(...arguments);
197
+ return payload;
198
+ }
199
+
200
+ /**
201
+ * Extracts the error message from a list of errors.
202
+ * Returns a default error message if the provided list is empty or undefined.
203
+ *
204
+ * @param {Array} errors - Array of error messages or objects.
205
+ * @return {string} - The extracted error message.
206
+ */
207
+ getErrorMessage(errors = []) {
208
+ return errors[0] ? errors[0] : DEFAULT_ERROR_MESSAGE;
209
+ }
210
+
211
+ /**
212
+ * Extracts errors from a payload.
213
+ * Assumes the payload contains an `errors` array; returns a default error message otherwise.
214
+ *
215
+ * @param {object} payload - The response payload.
216
+ * @return {Array} - Array of extracted errors or a default error message.
217
+ */
218
+ getResponseErrors(payload) {
219
+ return isArray(payload.errors) ? payload.errors : [DEFAULT_ERROR_MESSAGE];
220
+ }
221
+
222
+ /**
223
+ * Determines if the response status indicates an error.
224
+ * Checks both the HTTP status code and the presence of errors in the payload.
225
+ *
226
+ * @param {number} status - The HTTP status code.
227
+ * @param {object} payload - The response payload.
228
+ * @return {boolean} - True if the response indicates an error, false otherwise.
229
+ */
230
+ isErrorResponse(status, payload) {
231
+ return (status >= 400 && status < 600) || (!isBlank(payload) && isArray(payload.errors));
173
232
  }
174
233
  }
@@ -1,7 +1,12 @@
1
1
  export function initialize() {
2
- const socketClusterClientScript = document.createElement('script');
3
- socketClusterClientScript.src = '/assets/socketcluster-client.min.js';
4
- document.body.appendChild(socketClusterClientScript);
2
+ // Check if the script already exists
3
+ // Only insert the script tag if it doesn't already exist
4
+ if (!document.querySelector('script[data-socketcluster-client]')) {
5
+ const socketClusterClientScript = document.createElement('script');
6
+ socketClusterClientScript.setAttribute('data-socketcluster-client', '1');
7
+ socketClusterClientScript.src = '/assets/socketcluster-client.min.js';
8
+ document.body.appendChild(socketClusterClientScript);
9
+ }
5
10
  }
6
11
 
7
12
  export default {
@@ -12,7 +12,8 @@ export default class AppCacheService extends Service {
12
12
  @storageFor('local-cache') localCache;
13
13
 
14
14
  get cachePrefix() {
15
- return `${this.currentUser.id}:${this.currentUser.companyId}:`;
15
+ const userId = this.currentUser.id ?? 'anon';
16
+ return `${userId}:${this.currentUser.companyId}:`;
16
17
  }
17
18
 
18
19
  @action setEmberData(key, value, except = []) {
@@ -1,6 +1,7 @@
1
1
  import Service from '@ember/service';
2
2
  import Evented from '@ember/object/evented';
3
3
  import { inject as service } from '@ember/service';
4
+ import { tracked } from '@glimmer/tracking';
4
5
  import { dasherize } from '@ember/string';
5
6
  import { computed, get, action } from '@ember/object';
6
7
  import { isBlank } from '@ember/utils';
@@ -43,6 +44,16 @@ export default class CurrentUserService extends Service.extend(Evented) {
43
44
  */
44
45
  @service notifications;
45
46
 
47
+ /**
48
+ * Property to hold loaded user.
49
+ *
50
+ * @var {UserModel|Object}
51
+ * @memberof CurrentUserService
52
+ */
53
+ @tracked user = {
54
+ id: 'anon',
55
+ };
56
+
46
57
  /**
47
58
  * User options in localStorage
48
59
  *
@@ -9,6 +9,7 @@ import { singularize, pluralize } from 'ember-inflector';
9
9
  import { task } from 'ember-concurrency';
10
10
  import { storageFor } from 'ember-local-storage';
11
11
  import { intervalToDuration, parseISO } from 'date-fns';
12
+ import { decompress as decompressJson } from 'compress-json';
12
13
  import config from 'ember-get-config';
13
14
  import corslite from '../utils/corslite';
14
15
  import getMimeType from '../utils/get-mime-type';
@@ -226,22 +227,31 @@ export default class FetchService extends Service {
226
227
  *
227
228
  * @return {Promise}
228
229
  */
229
- parseJSON(response) {
230
- return new Promise((resolve, reject) =>
231
- response
232
- .json()
233
- .then((json) =>
234
- resolve({
235
- statusText: response.statusText,
236
- status: response.status,
237
- ok: response.ok,
238
- json,
239
- })
240
- )
241
- .catch(() => {
242
- reject(new Error('Oops! Something went wrong when handling your request.'));
243
- })
244
- );
230
+ async parseJSON(response) {
231
+ try {
232
+ const compressedHeader = await response.headers.get('x-compressed-json');
233
+ let json;
234
+
235
+ if (compressedHeader === '1') {
236
+ // Handle compressed json
237
+ const text = await response.text();
238
+ json = JSON.parse(text);
239
+ json = decompressJson(json);
240
+ json = JSON.parse(json);
241
+ } else {
242
+ // Handle regular json
243
+ json = await response.json();
244
+ }
245
+
246
+ return {
247
+ statusText: response.statusText,
248
+ status: response.status,
249
+ ok: response.ok,
250
+ json,
251
+ };
252
+ } catch (error) {
253
+ throw new Error('Error processing response: ' + error.message);
254
+ }
245
255
  }
246
256
 
247
257
  /**
@@ -203,7 +203,7 @@ export default class ThemeService extends Service {
203
203
  setTheme(theme = 'light') {
204
204
  document.body.classList.remove(`${this.currentTheme}-theme`);
205
205
  document.body.classList.add(`${theme}-theme`);
206
- this.currentUser.setOption(`theme`, theme);
206
+ this.currentUser.setOption('theme', theme);
207
207
  this.activeTheme = theme;
208
208
  }
209
209
 
@@ -259,6 +259,7 @@ export default class UniverseService extends Service.extend(Evented) {
259
259
  name: registryName,
260
260
  menuItems: [],
261
261
  menuPanels: [],
262
+ renderableComponents: [],
262
263
  ...options,
263
264
  };
264
265
 
@@ -426,6 +427,26 @@ export default class UniverseService extends Service.extend(Evented) {
426
427
  return [];
427
428
  }
428
429
 
430
+ /**
431
+ * Retrieves renderable components from a specified registry.
432
+ * This action checks the internal registry, identified by the given registry name,
433
+ * and returns the 'renderableComponents' if they are present and are an array.
434
+ *
435
+ * @action
436
+ * @param {string} registryName - The name of the registry to retrieve components from.
437
+ * @returns {Array} An array of renderable components from the specified registry, or an empty array if none found.
438
+ */
439
+ @action getRenderableComponentsFromRegistry(registryName) {
440
+ const internalRegistryName = this.createInternalRegistryName(registryName);
441
+ const registry = this[internalRegistryName];
442
+
443
+ if (!isBlank(registry) && isArray(registry.renderableComponents)) {
444
+ return registry.renderableComponents;
445
+ }
446
+
447
+ return [];
448
+ }
449
+
429
450
  /**
430
451
  * Loads a component from the specified registry based on a given slug and view.
431
452
  *
@@ -550,6 +571,34 @@ export default class UniverseService extends Service.extend(Evented) {
550
571
  });
551
572
  }
552
573
 
574
+ /**
575
+ * Registers a renderable component or an array of components into a specified registry.
576
+ * If a single component is provided, it is registered directly.
577
+ * If an array of components is provided, each component in the array is registered individually.
578
+ * The component is also registered into the specified engine.
579
+ *
580
+ * @param {string} engineName - The name of the engine to register the component(s) into.
581
+ * @param {string} registryName - The registry name where the component(s) should be registered.
582
+ * @param {Object|Array} component - The component or array of components to register.
583
+ */
584
+ registerRenderableComponent(engineName, registryName, component) {
585
+ if (isArray(component)) {
586
+ component.forEach((_) => this.registerRenderableComponent(registryName, _));
587
+ return;
588
+ }
589
+
590
+ // register component to engine
591
+ this.registerComponentInEngine(engineName, component);
592
+
593
+ // register to registry
594
+ const internalRegistryName = this.createInternalRegistryName(registryName);
595
+ if (isArray(this[internalRegistryName].renderableComponents)) {
596
+ this[internalRegistryName].renderableComponents.pushObject(component);
597
+ } else {
598
+ this[internalRegistryName].renderableComponents = [component];
599
+ }
600
+ }
601
+
553
602
  /**
554
603
  * Registers a new menu panel in a registry.
555
604
  *
package/index.js CHANGED
@@ -24,11 +24,11 @@ module.exports = {
24
24
 
25
25
  if (app.options['ember-cli-notifications'] !== undefined) {
26
26
  app.options['ember-cli-notifications'].autoClear = true;
27
- app.options['ember-cli-notifications'].clearDuration = 1000 * 5;
27
+ app.options['ember-cli-notifications'].clearDuration = 1000 * 3.5;
28
28
  } else {
29
29
  app.options['ember-cli-notifications'] = {
30
30
  autoClear: true,
31
- clearDuration: 1000 * 5,
31
+ clearDuration: 1000 * 3.5,
32
32
  };
33
33
  }
34
34
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/ember-core",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Provides all the core services, decorators and utilities for building a Fleetbase extension for the Console.",
5
5
  "keywords": [
6
6
  "fleetbase-core",
@@ -35,6 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@babel/core": "^7.23.2",
38
+ "compress-json": "^3.0.0",
38
39
  "date-fns": "^2.30.0",
39
40
  "ember-auto-import": "^2.6.3",
40
41
  "ember-cli-babel": "^8.2.0",
@@ -71,13 +72,13 @@
71
72
  "ember-cli-inject-live-reload": "^2.1.0",
72
73
  "ember-cli-sri": "^2.1.1",
73
74
  "ember-cli-terser": "^4.0.2",
75
+ "ember-data": "^4.12.5",
74
76
  "ember-file-upload": "8.4.0",
75
77
  "ember-load-initializers": "^2.1.2",
76
78
  "ember-page-title": "^8.0.0",
77
79
  "ember-qunit": "^8.0.1",
78
80
  "ember-resolver": "^11.0.1",
79
81
  "ember-source": "~5.4.0",
80
- "ember-data": "^4.12.5",
81
82
  "ember-source-channel-url": "^3.0.0",
82
83
  "ember-template-lint": "^5.11.2",
83
84
  "ember-try": "^3.0.0",