@fleetbase/ember-core 0.3.10 → 0.3.12
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.
- package/addon/exports/host-services.js +1 -0
- package/addon/exports/services.js +1 -0
- package/addon/services/app-cache.js +2 -1
- package/addon/services/crud.js +19 -0
- package/addon/services/current-user.js +50 -36
- package/addon/services/events.js +322 -0
- package/addon/services/resource-action.js +18 -0
- package/addon/services/universe.js +0 -1
- package/addon/utils/lookup-user-ip.js +221 -0
- package/app/services/events.js +1 -0
- package/package.json +1 -1
- package/BOOT_SEQUENCE_REFACTOR_GUIDE.md +0 -294
- package/UNIVERSE_REFACTOR_MIGRATION_GUIDE.md +0 -318
- package/UNIVERSE_REFACTOR_README.md +0 -220
|
@@ -13,7 +13,8 @@ export default class AppCacheService extends Service {
|
|
|
13
13
|
|
|
14
14
|
get cachePrefix() {
|
|
15
15
|
const userId = this.currentUser.id ?? 'anon';
|
|
16
|
-
|
|
16
|
+
const companyId = this.currentUser.companyId ?? 'no-org';
|
|
17
|
+
return `${userId}:${companyId}:`;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
@action setEmberData(key, value, except = []) {
|
package/addon/services/crud.js
CHANGED
|
@@ -17,6 +17,8 @@ export default class CrudService extends Service {
|
|
|
17
17
|
@service notifications;
|
|
18
18
|
@service store;
|
|
19
19
|
@service currentUser;
|
|
20
|
+
@service universe;
|
|
21
|
+
@service events;
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Generic deletion modal with options
|
|
@@ -43,6 +45,10 @@ export default class CrudService extends Service {
|
|
|
43
45
|
try {
|
|
44
46
|
const response = await model.destroyRecord();
|
|
45
47
|
this.notifications.success(successNotification);
|
|
48
|
+
|
|
49
|
+
// Track deletion event
|
|
50
|
+
this.events.trackResourceDeleted(model);
|
|
51
|
+
|
|
46
52
|
if (typeof options.onSuccess === 'function') {
|
|
47
53
|
options.onSuccess(model);
|
|
48
54
|
}
|
|
@@ -161,6 +167,10 @@ export default class CrudService extends Service {
|
|
|
161
167
|
);
|
|
162
168
|
|
|
163
169
|
this.notifications.success(response.message ?? successMessage);
|
|
170
|
+
|
|
171
|
+
// Track bulk action event
|
|
172
|
+
this.events.trackBulkAction(verb, selected);
|
|
173
|
+
|
|
164
174
|
if (typeof options.onSuccess === 'function') {
|
|
165
175
|
options.onSuccess(selected);
|
|
166
176
|
}
|
|
@@ -224,6 +234,9 @@ export default class CrudService extends Service {
|
|
|
224
234
|
}
|
|
225
235
|
)
|
|
226
236
|
.then(() => {
|
|
237
|
+
// Track export event
|
|
238
|
+
this.events.trackResourceExported(modelName, format, exportParams);
|
|
239
|
+
|
|
227
240
|
later(
|
|
228
241
|
this,
|
|
229
242
|
() => {
|
|
@@ -248,6 +261,7 @@ export default class CrudService extends Service {
|
|
|
248
261
|
* @param {Object} [options={}]
|
|
249
262
|
* @memberof CrudService
|
|
250
263
|
*/
|
|
264
|
+
|
|
251
265
|
@action import(modelName, options = {}) {
|
|
252
266
|
// always lowercase modelname
|
|
253
267
|
modelName = modelName.toLowerCase();
|
|
@@ -337,6 +351,11 @@ export default class CrudService extends Service {
|
|
|
337
351
|
|
|
338
352
|
try {
|
|
339
353
|
const response = await this.fetch.post(importEndpoint, { files }, fetchOptions);
|
|
354
|
+
|
|
355
|
+
// Track import event
|
|
356
|
+
const importCount = response?.imported?.length || response?.count || files.length;
|
|
357
|
+
this.events.trackResourceImported(modelName, importCount);
|
|
358
|
+
|
|
340
359
|
if (typeof options.onImportCompleted === 'function') {
|
|
341
360
|
options.onImportCompleted(response, files);
|
|
342
361
|
}
|
|
@@ -8,6 +8,7 @@ import { isBlank } from '@ember/utils';
|
|
|
8
8
|
import { alias } from '@ember/object/computed';
|
|
9
9
|
import { storageFor } from 'ember-local-storage';
|
|
10
10
|
import { debug } from '@ember/debug';
|
|
11
|
+
import lookupUserIp from '../utils/lookup-user-ip';
|
|
11
12
|
|
|
12
13
|
export default class CurrentUserService extends Service.extend(Evented) {
|
|
13
14
|
@service session;
|
|
@@ -16,6 +17,7 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
16
17
|
@service theme;
|
|
17
18
|
@service notifications;
|
|
18
19
|
@service intl;
|
|
20
|
+
@service events;
|
|
19
21
|
|
|
20
22
|
@tracked user = { id: 'anon' };
|
|
21
23
|
@tracked userSnapshot = { id: 'anon' };
|
|
@@ -65,14 +67,9 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
65
67
|
async load() {
|
|
66
68
|
if (this.session.isAuthenticated) {
|
|
67
69
|
const user = await this.store.findRecord('user', 'me');
|
|
68
|
-
const snapshot = await this.getUserSnapshot(user);
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
-
this.trigger('user.loaded', user);
|
|
73
|
-
|
|
74
|
-
// Set permissions
|
|
75
|
-
this.permissions = this.getUserPermissions(user);
|
|
71
|
+
// set user
|
|
72
|
+
this.setUser(user);
|
|
76
73
|
|
|
77
74
|
// Load preferences
|
|
78
75
|
await this.loadPreferences();
|
|
@@ -91,25 +88,9 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
91
88
|
|
|
92
89
|
try {
|
|
93
90
|
const user = await this.store.queryRecord('user', { me: true });
|
|
94
|
-
const snapshot = await this.getUserSnapshot(user);
|
|
95
|
-
|
|
96
|
-
// Set current user
|
|
97
|
-
this.set('user', user);
|
|
98
|
-
this.set('userSnapshot', snapshot);
|
|
99
|
-
this.trigger('user.loaded', user);
|
|
100
|
-
|
|
101
|
-
// Set permissions
|
|
102
|
-
this.permissions = this.getUserPermissions(user);
|
|
103
91
|
|
|
104
|
-
//
|
|
105
|
-
this.
|
|
106
|
-
|
|
107
|
-
// Set locale
|
|
108
|
-
if (user.locale) {
|
|
109
|
-
this.setLocale(user.locale);
|
|
110
|
-
} else {
|
|
111
|
-
await this.loadLocale();
|
|
112
|
-
}
|
|
92
|
+
// set user
|
|
93
|
+
this.setUser(user);
|
|
113
94
|
|
|
114
95
|
// Load user whois data
|
|
115
96
|
await this.loadWhois();
|
|
@@ -159,23 +140,34 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
159
140
|
}
|
|
160
141
|
|
|
161
142
|
async loadWhois() {
|
|
162
|
-
this.fetch.shouldResetCache();
|
|
163
|
-
|
|
164
143
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
);
|
|
144
|
+
// Use frontend IP lookup to get accurate user location
|
|
145
|
+
// This avoids the issue of server-side lookup returning server IP instead of user IP
|
|
146
|
+
const whois = await lookupUserIp({
|
|
147
|
+
timeout: 5000,
|
|
148
|
+
cache: true,
|
|
149
|
+
});
|
|
150
|
+
|
|
173
151
|
this.setOption('whois', whois);
|
|
174
152
|
this.whoisData = whois;
|
|
175
153
|
|
|
176
154
|
return whois;
|
|
177
155
|
} catch (error) {
|
|
178
|
-
|
|
156
|
+
console.error('[currentUser] Failed to load whois:', error);
|
|
157
|
+
this.notifications.warning('Unable to detect your location. Some features may use default settings.');
|
|
158
|
+
|
|
159
|
+
// Return fallback data with browser timezone
|
|
160
|
+
const fallback = {
|
|
161
|
+
city: null,
|
|
162
|
+
country_code: null,
|
|
163
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
164
|
+
_source: 'fallback',
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
this.setOption('whois', fallback);
|
|
168
|
+
this.whoisData = fallback;
|
|
169
|
+
|
|
170
|
+
return fallback;
|
|
179
171
|
}
|
|
180
172
|
}
|
|
181
173
|
|
|
@@ -309,4 +301,26 @@ export default class CurrentUserService extends Service.extend(Evented) {
|
|
|
309
301
|
|
|
310
302
|
return defaultValue;
|
|
311
303
|
}
|
|
304
|
+
|
|
305
|
+
async setUser(user) {
|
|
306
|
+
const snapshot = await this.getUserSnapshot(user);
|
|
307
|
+
|
|
308
|
+
// Set current user
|
|
309
|
+
this.set('user', user);
|
|
310
|
+
this.set('userSnapshot', snapshot);
|
|
311
|
+
this.trigger('user.loaded', user);
|
|
312
|
+
|
|
313
|
+
// Set permissions
|
|
314
|
+
this.permissions = this.getUserPermissions(user);
|
|
315
|
+
|
|
316
|
+
// Set environment from user option
|
|
317
|
+
this.theme.setEnvironment();
|
|
318
|
+
|
|
319
|
+
// Set locale
|
|
320
|
+
if (user.locale) {
|
|
321
|
+
this.setLocale(user.locale);
|
|
322
|
+
} else {
|
|
323
|
+
await this.loadLocale();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
312
326
|
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import Service, { inject as service } from '@ember/service';
|
|
2
|
+
import Evented from '@ember/object/evented';
|
|
3
|
+
import config from 'ember-get-config';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Events Service
|
|
7
|
+
*
|
|
8
|
+
* Provides a centralized event tracking system for Fleetbase.
|
|
9
|
+
* This service emits standardized events on both its own event bus and the universe service,
|
|
10
|
+
* allowing components, services, and engines to subscribe and react to application events.
|
|
11
|
+
*
|
|
12
|
+
* @class EventsService
|
|
13
|
+
* @extends Service
|
|
14
|
+
*/
|
|
15
|
+
export default class EventsService extends Service.extend(Evented) {
|
|
16
|
+
@service universe;
|
|
17
|
+
@service currentUser;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tracks the creation of a resource
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} resource - The created resource/model
|
|
23
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
24
|
+
*/
|
|
25
|
+
trackResourceCreated(resource, props = {}) {
|
|
26
|
+
const events = this.#getResourceEvents(resource, 'created');
|
|
27
|
+
const properties = this.#enrichProperties({
|
|
28
|
+
...this.#getSafeProperties(resource),
|
|
29
|
+
...props,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
events.forEach((eventName) => {
|
|
33
|
+
this.#trigger(eventName, resource, properties);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tracks the update of a resource
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} resource - The updated resource/model
|
|
41
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
42
|
+
*/
|
|
43
|
+
trackResourceUpdated(resource, props = {}) {
|
|
44
|
+
const events = this.#getResourceEvents(resource, 'updated');
|
|
45
|
+
const properties = this.#enrichProperties({
|
|
46
|
+
...this.#getSafeProperties(resource),
|
|
47
|
+
...props,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
events.forEach((eventName) => {
|
|
51
|
+
this.#trigger(eventName, resource, properties);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tracks the deletion of a resource
|
|
57
|
+
*
|
|
58
|
+
* @param {Object} resource - The deleted resource/model
|
|
59
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
60
|
+
*/
|
|
61
|
+
trackResourceDeleted(resource, props = {}) {
|
|
62
|
+
const events = this.#getResourceEvents(resource, 'deleted');
|
|
63
|
+
const properties = this.#enrichProperties({
|
|
64
|
+
...this.#getSafeProperties(resource),
|
|
65
|
+
...props,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
events.forEach((eventName) => {
|
|
69
|
+
this.#trigger(eventName, resource, properties);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tracks a bulk import of resources
|
|
75
|
+
*
|
|
76
|
+
* @param {String} modelName - The name of the model being imported
|
|
77
|
+
* @param {Number} count - Number of resources imported
|
|
78
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
79
|
+
*/
|
|
80
|
+
trackResourceImported(modelName, count, props = {}) {
|
|
81
|
+
const properties = this.#enrichProperties({
|
|
82
|
+
model_name: modelName,
|
|
83
|
+
count: count,
|
|
84
|
+
...props,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.#trigger('resource.imported', modelName, count, properties);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Tracks a resource export
|
|
92
|
+
*
|
|
93
|
+
* @param {String} modelName - The name of the model being exported
|
|
94
|
+
* @param {String} format - Export format (csv, xlsx, etc.)
|
|
95
|
+
* @param {Object} [params={}] - Export parameters/filters
|
|
96
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
97
|
+
*/
|
|
98
|
+
trackResourceExported(modelName, format, params = {}, props = {}) {
|
|
99
|
+
const properties = this.#enrichProperties({
|
|
100
|
+
model_name: modelName,
|
|
101
|
+
export_format: format,
|
|
102
|
+
has_filters: !!(params && Object.keys(params).length > 0),
|
|
103
|
+
...props,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.#trigger('resource.exported', modelName, format, params, properties);
|
|
107
|
+
|
|
108
|
+
// Also trigger model-specific export event
|
|
109
|
+
const specificEvent = `${modelName}.exported`;
|
|
110
|
+
this.#trigger(specificEvent, modelName, format, params, properties);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Tracks a bulk action on multiple resources
|
|
115
|
+
*
|
|
116
|
+
* @param {String} verb - The action verb (delete, archive, etc.)
|
|
117
|
+
* @param {Array} resources - Array of selected resources
|
|
118
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
119
|
+
*/
|
|
120
|
+
trackBulkAction(verb, resources, props = {}) {
|
|
121
|
+
const firstResource = resources && resources.length > 0 ? resources[0] : null;
|
|
122
|
+
const modelName = this.#getModelName(firstResource);
|
|
123
|
+
|
|
124
|
+
const properties = this.#enrichProperties({
|
|
125
|
+
action: verb,
|
|
126
|
+
count: resources?.length || 0,
|
|
127
|
+
model_name: modelName,
|
|
128
|
+
...props,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
this.#trigger('resource.bulk_action', verb, resources, firstResource, properties);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Tracks when the current user is loaded (session initialized)
|
|
136
|
+
*
|
|
137
|
+
* @param {Object} user - The user object
|
|
138
|
+
* @param {Object} organization - The organization object
|
|
139
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
140
|
+
*/
|
|
141
|
+
trackUserLoaded(user, organization, props = {}) {
|
|
142
|
+
const properties = this.#enrichProperties({
|
|
143
|
+
user_id: user?.id,
|
|
144
|
+
organization_id: organization?.id,
|
|
145
|
+
organization_name: organization?.name,
|
|
146
|
+
...props,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
this.#trigger('user.loaded', user, organization, properties);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Tracks when a user session is terminated
|
|
154
|
+
*
|
|
155
|
+
* @param {Number} duration - Session duration in seconds
|
|
156
|
+
* @param {Object} [props={}] - Additional properties to include
|
|
157
|
+
*/
|
|
158
|
+
trackSessionTerminated(duration, props = {}) {
|
|
159
|
+
const properties = this.#enrichProperties({
|
|
160
|
+
session_duration: duration,
|
|
161
|
+
...props,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
this.#trigger('session.terminated', duration, properties);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Tracks a generic custom event
|
|
169
|
+
*
|
|
170
|
+
* @param {String} eventName - The event name (dot notation)
|
|
171
|
+
* @param {Object} [props={}] - Event properties
|
|
172
|
+
*/
|
|
173
|
+
trackEvent(eventName, props = {}) {
|
|
174
|
+
const properties = this.#enrichProperties(props);
|
|
175
|
+
this.#trigger(eventName, properties);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Checks if event tracking is enabled
|
|
180
|
+
*
|
|
181
|
+
* @returns {Boolean}
|
|
182
|
+
*/
|
|
183
|
+
isEnabled() {
|
|
184
|
+
const eventsConfig = config?.events || {};
|
|
185
|
+
return eventsConfig.enabled !== false; // Enabled by default
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// =========================================================================
|
|
189
|
+
// Private Methods (using # syntax)
|
|
190
|
+
// =========================================================================
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Triggers an event on both the events service and universe service
|
|
194
|
+
*
|
|
195
|
+
* This dual event system allows listeners to subscribe to events on either:
|
|
196
|
+
* - this.events.on('event.name', handler) - Local listeners
|
|
197
|
+
* - this.universe.on('event.name', handler) - Cross-engine listeners
|
|
198
|
+
*
|
|
199
|
+
* @private
|
|
200
|
+
* @param {String} eventName - The event name
|
|
201
|
+
* @param {...*} args - Arguments to pass to event listeners
|
|
202
|
+
*/
|
|
203
|
+
#trigger(eventName, ...args) {
|
|
204
|
+
if (!this.isEnabled()) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Debug logging if enabled
|
|
209
|
+
if (config?.events?.debug) {
|
|
210
|
+
console.log(`[Events] ${eventName}`, args);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Trigger on events service (local listeners)
|
|
214
|
+
this.trigger(eventName, ...args);
|
|
215
|
+
|
|
216
|
+
// Trigger on universe service (cross-engine listeners)
|
|
217
|
+
if (this.universe) {
|
|
218
|
+
this.universe.trigger(eventName, ...args);
|
|
219
|
+
} else {
|
|
220
|
+
console.warn('[Events] Universe service not available');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Generates both generic and specific event names for a resource action
|
|
226
|
+
*
|
|
227
|
+
* @private
|
|
228
|
+
* @param {Object} resource - The resource/model
|
|
229
|
+
* @param {String} action - The action (created, updated, deleted)
|
|
230
|
+
* @returns {Array<String>} Array of event names
|
|
231
|
+
*/
|
|
232
|
+
#getResourceEvents(resource, action) {
|
|
233
|
+
const modelName = this.#getModelName(resource);
|
|
234
|
+
return [`resource.${action}`, `${modelName}.${action}`];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Extracts safe, serializable properties from a resource
|
|
239
|
+
*
|
|
240
|
+
* @private
|
|
241
|
+
* @param {Object} resource - The resource/model
|
|
242
|
+
* @returns {Object} Safe properties object
|
|
243
|
+
*/
|
|
244
|
+
#getSafeProperties(resource) {
|
|
245
|
+
if (!resource) {
|
|
246
|
+
return {};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const props = {
|
|
250
|
+
id: resource.id,
|
|
251
|
+
model_name: this.#getModelName(resource),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Add common properties if available
|
|
255
|
+
const commonProps = ['name', 'status', 'type', 'slug', 'public_id'];
|
|
256
|
+
commonProps.forEach((prop) => {
|
|
257
|
+
if (resource[prop] !== undefined && resource[prop] !== null) {
|
|
258
|
+
props[prop] = resource[prop];
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return props;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Enriches properties with user, organization, and timestamp context
|
|
267
|
+
*
|
|
268
|
+
* @private
|
|
269
|
+
* @param {Object} props - Base properties
|
|
270
|
+
* @returns {Object} Enriched properties
|
|
271
|
+
*/
|
|
272
|
+
#enrichProperties(props = {}) {
|
|
273
|
+
const eventsConfig = config?.events || {};
|
|
274
|
+
const enrichConfig = eventsConfig.enrich || {};
|
|
275
|
+
const enriched = { ...props };
|
|
276
|
+
|
|
277
|
+
// Add user context if enabled
|
|
278
|
+
if (enrichConfig.user !== false && this.currentUser?.user) {
|
|
279
|
+
enriched.user_id = this.currentUser.user.id;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Add organization context if enabled
|
|
283
|
+
if (enrichConfig.organization !== false && this.currentUser?.organization) {
|
|
284
|
+
enriched.organization_id = this.currentUser.organization.id;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Add timestamp if enabled
|
|
288
|
+
if (enrichConfig.timestamp !== false) {
|
|
289
|
+
enriched.timestamp = new Date().toISOString();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return enriched;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Safely extracts the model name from a resource
|
|
297
|
+
*
|
|
298
|
+
* @private
|
|
299
|
+
* @param {Object} resource - The resource/model
|
|
300
|
+
* @returns {String} Model name or 'unknown'
|
|
301
|
+
*/
|
|
302
|
+
#getModelName(resource) {
|
|
303
|
+
if (!resource) {
|
|
304
|
+
return 'unknown';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Try multiple ways to get model name
|
|
308
|
+
if (resource.constructor?.modelName) {
|
|
309
|
+
return resource.constructor.modelName;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (resource._internalModel?.modelName) {
|
|
313
|
+
return resource._internalModel.modelName;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (resource.modelName) {
|
|
317
|
+
return resource.modelName;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return 'unknown';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -29,6 +29,8 @@ export default class ResourceActionService extends Service {
|
|
|
29
29
|
@service abilities;
|
|
30
30
|
@service tableContext;
|
|
31
31
|
@service resourceContextPanel;
|
|
32
|
+
@service universe;
|
|
33
|
+
@service events;
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Getter for router, attempt to use hostRouter if from engine
|
|
@@ -299,6 +301,9 @@ export default class ResourceActionService extends Service {
|
|
|
299
301
|
})
|
|
300
302
|
);
|
|
301
303
|
|
|
304
|
+
// Track creation event
|
|
305
|
+
this.events.trackResourceCreated(record);
|
|
306
|
+
|
|
302
307
|
if (options.refresh) {
|
|
303
308
|
this.refresh();
|
|
304
309
|
}
|
|
@@ -329,6 +334,9 @@ export default class ResourceActionService extends Service {
|
|
|
329
334
|
})
|
|
330
335
|
);
|
|
331
336
|
|
|
337
|
+
// Track update event
|
|
338
|
+
this.events.trackResourceUpdated(record);
|
|
339
|
+
|
|
332
340
|
if (options.refresh) {
|
|
333
341
|
this.refresh();
|
|
334
342
|
}
|
|
@@ -362,6 +370,13 @@ export default class ResourceActionService extends Service {
|
|
|
362
370
|
})
|
|
363
371
|
);
|
|
364
372
|
|
|
373
|
+
// Track save event (create or update)
|
|
374
|
+
if (isNew) {
|
|
375
|
+
this.events.trackResourceCreated(record);
|
|
376
|
+
} else {
|
|
377
|
+
this.events.trackResourceUpdated(record);
|
|
378
|
+
}
|
|
379
|
+
|
|
365
380
|
if (options.refresh) {
|
|
366
381
|
this.refresh();
|
|
367
382
|
}
|
|
@@ -409,6 +424,9 @@ export default class ResourceActionService extends Service {
|
|
|
409
424
|
})
|
|
410
425
|
);
|
|
411
426
|
|
|
427
|
+
// Track deletion event
|
|
428
|
+
this.events.trackResourceDeleted(record);
|
|
429
|
+
|
|
412
430
|
if (options.refresh) {
|
|
413
431
|
this.refresh();
|
|
414
432
|
}
|
|
@@ -33,7 +33,6 @@ export default class UniverseService extends Service.extend(Evented) {
|
|
|
33
33
|
@service router;
|
|
34
34
|
@service intl;
|
|
35
35
|
@service urlSearchParams;
|
|
36
|
-
|
|
37
36
|
@tracked applicationInstance;
|
|
38
37
|
@tracked initialLocation = { ...window.location };
|
|
39
38
|
@tracked bootCallbacks = A([]);
|