@adventurelabs/scout-core 1.0.110 → 1.0.112

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.
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useCallback, useRef, useMemo } from "react";
2
2
  import { useAppDispatch } from "../store/hooks";
3
3
  import { useStore } from "react-redux";
4
- import { EnumScoutStateStatus, setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setUser, setDataSource, setDataSourceInfo, } from "../store/scout";
4
+ import { EnumScoutStateStatus, setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setCacheLoadDuration, setUser, setDataSource, setDataSourceInfo, } from "../store/scout";
5
5
  import { EnumHerdModulesLoadingState } from "../types/herd_module";
6
6
  import { server_load_herd_modules } from "../helpers/herds";
7
7
  import { scoutCache } from "../helpers/cache";
@@ -91,37 +91,104 @@ export function useScoutRefresh(options = {}) {
91
91
  }
92
92
  return true;
93
93
  }, []);
94
- // Helper function to normalize herd modules for comparison (excludes metadata)
95
- const normalizeHerdModulesForComparison = useCallback((herdModules) => {
94
+ // Helper function to sort herd modules consistently by ID
95
+ const sortHerdModulesById = useCallback((herdModules) => {
96
96
  if (!Array.isArray(herdModules))
97
97
  return herdModules;
98
- return herdModules.map((hm) => {
99
- if (!hm || typeof hm !== "object")
100
- return hm;
101
- // Create a copy without metadata fields that don't represent business data changes
102
- const { timestamp_last_refreshed, ...businessData } = hm;
103
- return businessData;
98
+ return [...herdModules].sort((a, b) => {
99
+ const aId = a?.herd?.id || 0;
100
+ const bId = b?.herd?.id || 0;
101
+ return aId - bId;
104
102
  });
105
103
  }, []);
106
- // Helper function to conditionally dispatch only if business data has changed
107
- const conditionalDispatch = useCallback((newData, currentData, actionCreator, dataType, useNormalizedComparison = false) => {
104
+ // Helper function to find differences between herd modules for debugging
105
+ const findHerdModulesDifferences = useCallback((newData, currentData) => {
106
+ if (!Array.isArray(newData) || !Array.isArray(currentData)) {
107
+ return `Array type mismatch: new=${Array.isArray(newData)}, current=${Array.isArray(currentData)}`;
108
+ }
109
+ if (newData.length !== currentData.length) {
110
+ return `Array length mismatch: new=${newData.length}, current=${currentData.length}`;
111
+ }
112
+ // Sort both arrays by herd ID for consistent comparison
113
+ const sortedNew = sortHerdModulesById(newData);
114
+ const sortedCurrent = sortHerdModulesById(currentData);
115
+ const differences = [];
116
+ for (let i = 0; i < sortedNew.length; i++) {
117
+ const newHerd = sortedNew[i];
118
+ const currentHerd = sortedCurrent[i];
119
+ if (!newHerd || !currentHerd) {
120
+ differences.push(`Herd ${i}: null/undefined mismatch`);
121
+ continue;
122
+ }
123
+ // Get herd identifiers for better debugging
124
+ const newHerdId = newHerd.herd?.id || "unknown";
125
+ const currentHerdId = currentHerd.herd?.id || "unknown";
126
+ const newHerdName = newHerd.herd?.name || "unknown";
127
+ const currentHerdName = currentHerd.herd?.name || "unknown";
128
+ // Check if herd IDs match (data shuffling detection)
129
+ if (newHerdId !== currentHerdId) {
130
+ differences.push(`Herd ${i}: ID mismatch - expected ${currentHerdId}(${currentHerdName}) got ${newHerdId}(${newHerdName})`);
131
+ }
132
+ // Check top-level fields
133
+ const fieldsToCheck = [
134
+ "timestamp_last_refreshed",
135
+ "total_events",
136
+ "total_events_with_filters",
137
+ "events_page_index",
138
+ ];
139
+ fieldsToCheck.forEach((field) => {
140
+ if (newHerd[field] !== currentHerd[field]) {
141
+ differences.push(`Herd ${i}(${newHerdName}).${field}: ${currentHerd[field]} → ${newHerd[field]}`);
142
+ }
143
+ });
144
+ // Check array lengths for nested data
145
+ const arrayFields = [
146
+ "devices",
147
+ "events",
148
+ "plans",
149
+ "zones",
150
+ "sessions",
151
+ "layers",
152
+ "providers",
153
+ ];
154
+ arrayFields.forEach((field) => {
155
+ const newArray = newHerd[field];
156
+ const currentArray = currentHerd[field];
157
+ if (Array.isArray(newArray) && Array.isArray(currentArray)) {
158
+ if (newArray.length !== currentArray.length) {
159
+ differences.push(`Herd ${i}(${newHerdName}).${field}[]: length ${currentArray.length} → ${newArray.length}`);
160
+ }
161
+ }
162
+ });
163
+ }
164
+ return differences.length > 0
165
+ ? differences.join(", ")
166
+ : "No significant differences found";
167
+ }, []);
168
+ // Helper function to conditionally dispatch only if data has changed
169
+ const conditionalDispatch = useCallback((newData, currentData, actionCreator, dataType, enableDebugging = false) => {
170
+ // For herd modules, sort both datasets by ID before comparison
108
171
  let dataToCompare = newData;
109
172
  let currentToCompare = currentData;
110
- // For herd modules, use normalized comparison that ignores metadata
111
- if (useNormalizedComparison && dataType.includes("Herd modules")) {
112
- dataToCompare = normalizeHerdModulesForComparison(newData);
113
- currentToCompare = normalizeHerdModulesForComparison(currentData);
173
+ if (dataType.includes("Herd modules")) {
174
+ dataToCompare = sortHerdModulesById(newData);
175
+ currentToCompare = sortHerdModulesById(currentData);
114
176
  }
115
177
  if (!deepEqual(dataToCompare, currentToCompare)) {
116
- console.log(`[useScoutRefresh] ${dataType} business data changed, updating store`);
117
- dispatch(actionCreator(newData)); // Always dispatch the full data including timestamps
178
+ console.log(`[useScoutRefresh] ${dataType} data changed, updating store`);
179
+ // Add debugging for herd modules to see what actually changed
180
+ if (enableDebugging && dataType.includes("Herd modules")) {
181
+ const differences = findHerdModulesDifferences(newData, currentData);
182
+ console.log(`[useScoutRefresh] ${dataType} differences: ${differences}`);
183
+ }
184
+ dispatch(actionCreator(newData)); // Always dispatch original unsorted data
118
185
  return true;
119
186
  }
120
187
  else {
121
- console.log(`[useScoutRefresh] ${dataType} business data unchanged, skipping store update`);
188
+ console.log(`[useScoutRefresh] ${dataType} data unchanged, skipping store update`);
122
189
  return false;
123
190
  }
124
- }, [dispatch, deepEqual, normalizeHerdModulesForComparison]);
191
+ }, [dispatch, deepEqual, findHerdModulesDifferences, sortHerdModulesById]);
125
192
  // Helper function to handle IndexedDB errors - memoized for stability
126
193
  const handleIndexedDbError = useCallback(async (error, operation, retryFn) => {
127
194
  if (error instanceof Error &&
@@ -163,6 +230,7 @@ export function useScoutRefresh(options = {}) {
163
230
  const cacheResult = await scoutCache.getHerdModules();
164
231
  cacheLoadDuration = Date.now() - cacheStartTime;
165
232
  timingRefs.current.cacheLoadDuration = cacheLoadDuration;
233
+ dispatch(setCacheLoadDuration(cacheLoadDuration));
166
234
  if (cacheResult.data && cacheResult.data.length > 0) {
167
235
  cachedHerdModules = cacheResult.data;
168
236
  console.log(`[useScoutRefresh] Loaded ${cachedHerdModules.length} herd modules from cache in ${cacheLoadDuration}ms (age: ${Math.round(cacheResult.age / 1000)}s, stale: ${cacheResult.isStale})`);
@@ -14,6 +14,7 @@ export interface ScoutState {
14
14
  herd_modules_api_duration_ms: number | null;
15
15
  user_api_duration_ms: number | null;
16
16
  data_processing_duration_ms: number | null;
17
+ cache_load_duration_ms: number | null;
17
18
  active_herd_id: string | null;
18
19
  active_device_id: string | null;
19
20
  lastRefreshed: number;
@@ -54,6 +55,10 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
54
55
  payload: any;
55
56
  type: string;
56
57
  }) => void;
58
+ setCacheLoadDuration: (state: import("immer").WritableDraft<ScoutState>, action: {
59
+ payload: any;
60
+ type: string;
61
+ }) => void;
57
62
  setActiveHerdId: (state: import("immer").WritableDraft<ScoutState>, action: {
58
63
  payload: any;
59
64
  type: string;
@@ -155,6 +160,6 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
155
160
  type: string;
156
161
  }) => void;
157
162
  }, "scout", "scout", import("@reduxjs/toolkit").SliceSelectors<ScoutState>>;
158
- export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadingState">, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadedInMs">, setHerdModulesApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiDuration">, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUserApiDuration">, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataProcessingDuration">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, setDataSource: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSource">, setDataSourceInfo: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSourceInfo">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, addSessionToStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addSessionToStore">, deleteSessionFromStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteSessionFromStore">, updateSessionInStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionInStore">, setActiveHerdGpsTrackersConnectivity: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdGpsTrackersConnectivity">;
163
+ export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadingState">, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadedInMs">, setHerdModulesApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiDuration">, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUserApiDuration">, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataProcessingDuration">, setCacheLoadDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setCacheLoadDuration">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, setDataSource: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSource">, setDataSourceInfo: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSourceInfo">, appendEventsToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendEventsToHerdModule">, replaceEventsForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/replaceEventsForHerdModule">, updateEventValuesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateEventValuesForHerdModule">, updatePageIndexForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePageIndexForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addTag">, deleteTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteTag">, updateTag: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateTag">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, addSessionToStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addSessionToStore">, deleteSessionFromStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteSessionFromStore">, updateSessionInStore: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionInStore">, setActiveHerdGpsTrackersConnectivity: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdGpsTrackersConnectivity">;
159
164
  declare const _default: import("redux").Reducer<ScoutState>;
160
165
  export default _default;
@@ -15,6 +15,7 @@ const initialState = {
15
15
  herd_modules_api_duration_ms: null,
16
16
  user_api_duration_ms: null,
17
17
  data_processing_duration_ms: null,
18
+ cache_load_duration_ms: null,
18
19
  lastRefreshed: 0,
19
20
  active_herd_id: null,
20
21
  active_device_id: null,
@@ -50,6 +51,9 @@ export const scoutSlice = createSlice({
50
51
  setDataProcessingDuration: (state, action) => {
51
52
  state.data_processing_duration_ms = action.payload;
52
53
  },
54
+ setCacheLoadDuration: (state, action) => {
55
+ state.cache_load_duration_ms = action.payload;
56
+ },
53
57
  setActiveHerdId: (state, action) => {
54
58
  state.active_herd_id = action.payload;
55
59
  state.active_herd_gps_trackers_connectivity = {};
@@ -265,5 +269,5 @@ export const scoutSlice = createSlice({
265
269
  },
266
270
  });
267
271
  // Action creators are generated for each case reducer function
268
- export const { setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setActiveHerdId, setActiveDeviceId, setDataSource, setDataSourceInfo, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, setActiveHerdGpsTrackersConnectivity, } = scoutSlice.actions;
272
+ export const { setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiDuration, setUserApiDuration, setDataProcessingDuration, setCacheLoadDuration, setActiveHerdId, setActiveDeviceId, setDataSource, setDataSourceInfo, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, setActiveHerdGpsTrackersConnectivity, } = scoutSlice.actions;
269
273
  export default scoutSlice.reducer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.110",
3
+ "version": "1.0.112",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",