@adventurelabs/scout-core 1.0.29 → 1.0.31

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,6 +1,11 @@
1
1
  import { IEvent } from "../types/db";
2
2
  import { IWebResponseCompatible } from "../types/requests";
3
- export declare function server_get_events_by_herd(herd_id: number): Promise<IWebResponseCompatible<IEvent[]>>;
4
- export declare function server_get_more_events_by_herd(herd_id: number, offset: number, page_count?: number): Promise<IWebResponseCompatible<IEvent[]>>;
5
- export declare function server_get_total_events_by_herd(herd_id: number): Promise<IWebResponseCompatible<number>>;
3
+ export declare enum EnumSessionsVisibility {
4
+ Only = 0,
5
+ Exclude = 1,
6
+ Combine = 2
7
+ }
8
+ export declare function server_get_events_by_herd(herd_id: number, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<IEvent[]>>;
9
+ export declare function server_get_more_events_by_herd(herd_id: number, offset: number, page_count: number | undefined, includeSessionEvents: boolean): Promise<IWebResponseCompatible<IEvent[]>>;
10
+ export declare function server_get_total_events_by_herd(herd_id: number, sessions_visibility: EnumSessionsVisibility): Promise<IWebResponseCompatible<number>>;
6
11
  export declare function server_create_event(newEvent: any): Promise<IWebResponseCompatible<boolean>>;
@@ -2,18 +2,31 @@
2
2
  import { newServerClient } from "../supabase/server";
3
3
  import { EnumWebResponse, IWebResponse, } from "../types/requests";
4
4
  import { addSignedUrlsToEvents } from "./storage";
5
- export async function server_get_events_by_herd(herd_id) {
5
+ export var EnumSessionsVisibility;
6
+ (function (EnumSessionsVisibility) {
7
+ EnumSessionsVisibility[EnumSessionsVisibility["Only"] = 0] = "Only";
8
+ EnumSessionsVisibility[EnumSessionsVisibility["Exclude"] = 1] = "Exclude";
9
+ EnumSessionsVisibility[EnumSessionsVisibility["Combine"] = 2] = "Combine";
10
+ })(EnumSessionsVisibility || (EnumSessionsVisibility = {}));
11
+ export async function server_get_events_by_herd(herd_id, sessions_visibility) {
6
12
  const supabase = await newServerClient();
7
13
  // fetch events and include devices
8
14
  // sort by timestamp
9
- const { data } = await supabase
15
+ let query = supabase
10
16
  .from("events")
11
17
  .select(`
12
18
  *,
13
19
  devices: devices!inner(*)
14
20
  `)
15
- .eq("devices.herd_id", herd_id)
16
- .order("timestamp_observation", { ascending: false });
21
+ .eq("devices.herd_id", herd_id);
22
+ // Apply session filter based on sessions_visibility
23
+ if (sessions_visibility === EnumSessionsVisibility.Only) {
24
+ query = query.not("session_id", "is", null);
25
+ }
26
+ else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
27
+ query = query.is("session_id", null);
28
+ }
29
+ const { data } = await query.order("timestamp_observation", { ascending: false });
17
30
  // Add signed URLs to events using the same client
18
31
  const eventsWithSignedUrls = data
19
32
  ? await addSignedUrlsToEvents(data, supabase)
@@ -22,21 +35,29 @@ export async function server_get_events_by_herd(herd_id) {
22
35
  let response = IWebResponse.success(eventsWithSignedUrls);
23
36
  return response.to_compatible();
24
37
  }
25
- export async function server_get_more_events_by_herd(herd_id, offset, page_count = 10) {
38
+ export async function server_get_more_events_by_herd(herd_id, offset, page_count = 10, includeSessionEvents) {
26
39
  const from = offset * page_count;
27
40
  const to = from + page_count - 1;
28
41
  const supabase = await newServerClient();
29
42
  // fetch events and include devices
30
43
  // sort by timestamp
31
- const { data } = await supabase
44
+ let query = supabase
32
45
  .from("events")
33
46
  .select(`
34
47
  *,
35
48
  devices: devices!inner(*)
36
49
  `)
37
50
  .eq("devices.herd_id", herd_id)
38
- .range(from, to)
39
- .order("timestamp_observation", { ascending: false });
51
+ .range(from, to);
52
+ if (includeSessionEvents) {
53
+ // Include only events that have a session_id
54
+ query = query.not("session_id", "is", null);
55
+ }
56
+ else {
57
+ // Include only events that don't have a session_id
58
+ query = query.is("session_id", null);
59
+ }
60
+ const { data } = await query.order("timestamp_observation", { ascending: false });
40
61
  // Add signed URLs to events using the same client
41
62
  const eventsWithSignedUrls = data
42
63
  ? await addSignedUrlsToEvents(data, supabase)
@@ -46,12 +67,19 @@ export async function server_get_more_events_by_herd(herd_id, offset, page_count
46
67
  return response.to_compatible();
47
68
  }
48
69
  // function to get total number of events for a herd
49
- export async function server_get_total_events_by_herd(herd_id) {
70
+ export async function server_get_total_events_by_herd(herd_id, sessions_visibility) {
50
71
  const supabase = await newServerClient();
51
- // call public.get_total_events_for_herd(herd_id)
52
- const { data, error } = await supabase.rpc("get_total_events_for_herd", {
53
- herd_id_caller: herd_id,
54
- });
72
+ let query = supabase
73
+ .from("events")
74
+ .select("id", { count: "exact", head: true })
75
+ .eq("devices.herd_id", herd_id);
76
+ if (sessions_visibility === EnumSessionsVisibility.Only) {
77
+ query = query.not("session_id", "is", null);
78
+ }
79
+ else if (sessions_visibility === EnumSessionsVisibility.Exclude) {
80
+ query = query.is("session_id", null);
81
+ }
82
+ const { count, error } = await query;
55
83
  if (error) {
56
84
  return {
57
85
  status: EnumWebResponse.ERROR,
@@ -60,7 +88,7 @@ export async function server_get_total_events_by_herd(herd_id) {
60
88
  };
61
89
  }
62
90
  else {
63
- return IWebResponse.success(data).to_compatible();
91
+ return IWebResponse.success(count || 0).to_compatible();
64
92
  }
65
93
  }
66
94
  export async function server_create_event(newEvent) {
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { useAppDispatch } from "../store/hooks";
3
3
  import { useEffect, useRef } from "react";
4
- import { addDevice, addPlan, addTag, deleteDevice, deletePlan, deleteTag, updateDevice, updatePlan, updateTag, } from "../store/scout";
4
+ import { addDevice, addPlan, addTag, addSessionToStore, deleteDevice, deletePlan, deleteSessionFromStore, deleteTag, updateDevice, updatePlan, updateSessionInStore, updateTag, } from "../store/scout";
5
5
  export function useScoutDbListener(scoutSupabase) {
6
6
  const supabase = useRef(null);
7
7
  const channels = useRef([]);
@@ -56,6 +56,33 @@ export function useScoutDbListener(scoutSupabase) {
56
56
  console.log("[DB Listener] Plan UPDATE received:", payload.new);
57
57
  dispatch(updatePlan(payload.new));
58
58
  }
59
+ function handleSessionInserts(payload) {
60
+ console.log("[DB Listener] Session INSERT received:", payload.new);
61
+ dispatch(addSessionToStore(payload.new));
62
+ }
63
+ function handleSessionDeletes(payload) {
64
+ console.log("[DB Listener] Session DELETE received:", payload.old);
65
+ dispatch(deleteSessionFromStore(payload.old));
66
+ }
67
+ function handleSessionUpdates(payload) {
68
+ console.log("[DB Listener] Session UPDATE received:", payload.new);
69
+ dispatch(updateSessionInStore(payload.new));
70
+ }
71
+ function handleConnectivityInserts(payload) {
72
+ console.log("[DB Listener] Connectivity INSERT received:", payload.new);
73
+ // For now, we'll just log connectivity changes since they're related to sessions
74
+ // In the future, we might want to update session connectivity data
75
+ }
76
+ function handleConnectivityDeletes(payload) {
77
+ console.log("[DB Listener] Connectivity DELETE received:", payload.old);
78
+ // For now, we'll just log connectivity changes since they're related to sessions
79
+ // In the future, we might want to update session connectivity data
80
+ }
81
+ function handleConnectivityUpdates(payload) {
82
+ console.log("[DB Listener] Connectivity UPDATE received:", payload.new);
83
+ // For now, we'll just log connectivity changes since they're related to sessions
84
+ // In the future, we might want to update session connectivity data
85
+ }
59
86
  useEffect(() => {
60
87
  console.log("=== SCOUT DB LISTENER DEBUG ===");
61
88
  console.log("[DB Listener] Using shared Supabase client from ScoutRefreshProvider context");
@@ -91,12 +118,12 @@ export function useScoutDbListener(scoutSupabase) {
91
118
  .on("postgres_changes", { event: "INSERT", schema: "public", table: "tags" }, handleTagInserts)
92
119
  .on("postgres_changes", { event: "DELETE", schema: "public", table: "tags" }, handleTagDeletes)
93
120
  .on("postgres_changes", { event: "UPDATE", schema: "public", table: "tags" }, handleTagUpdates)
94
- .on("postgres_changes", { event: "INSERT", schema: "public", table: "connectivity" }, handleTagUpdates)
95
- .on("postgres_changes", { event: "DELETE", schema: "public", table: "connectivity" }, handleTagUpdates)
96
- .on("postgres_changes", { event: "UPDATE", schema: "public", table: "connectivity" }, handleTagUpdates)
97
- .on("postgres_changes", { event: "INSERT", schema: "public", table: "sessions" }, handleTagUpdates)
98
- .on("postgres_changes", { event: "DELETE", schema: "public", table: "sessions" }, handleTagUpdates)
99
- .on("postgres_changes", { event: "UPDATE", schema: "public", table: "sessions" }, handleTagUpdates)
121
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "connectivity" }, handleConnectivityInserts)
122
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "connectivity" }, handleConnectivityDeletes)
123
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "connectivity" }, handleConnectivityUpdates)
124
+ .on("postgres_changes", { event: "INSERT", schema: "public", table: "sessions" }, handleSessionInserts)
125
+ .on("postgres_changes", { event: "DELETE", schema: "public", table: "sessions" }, handleSessionDeletes)
126
+ .on("postgres_changes", { event: "UPDATE", schema: "public", table: "sessions" }, handleSessionUpdates)
100
127
  .subscribe((status) => {
101
128
  console.log("[DB Listener] Subscription status:", status);
102
129
  if (status === "SUBSCRIBED") {
@@ -93,11 +93,23 @@ export declare const scoutSlice: import("@reduxjs/toolkit").Slice<ScoutState, {
93
93
  payload: any;
94
94
  type: string;
95
95
  }) => void;
96
+ addSessionToStore: (state: import("immer").WritableDraft<ScoutState>, action: {
97
+ payload: any;
98
+ type: string;
99
+ }) => void;
100
+ deleteSessionFromStore: (state: import("immer").WritableDraft<ScoutState>, action: {
101
+ payload: any;
102
+ type: string;
103
+ }) => void;
104
+ updateSessionInStore: (state: import("immer").WritableDraft<ScoutState>, action: {
105
+ payload: any;
106
+ type: string;
107
+ }) => void;
96
108
  setUser: (state: import("immer").WritableDraft<ScoutState>, action: {
97
109
  payload: any;
98
110
  type: string;
99
111
  }) => void;
100
112
  }, "scout", "scout", import("@reduxjs/toolkit").SliceSelectors<ScoutState>>;
101
- export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, 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">;
113
+ export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, 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">;
102
114
  declare const _default: import("redux").Reducer<ScoutState>;
103
115
  export default _default;
@@ -192,11 +192,38 @@ export const scoutSlice = createSlice({
192
192
  herd_module.events_page_index = new_page_index;
193
193
  }
194
194
  },
195
+ addSessionToStore: (state, action) => {
196
+ const session = action.payload;
197
+ // Find the herd module that contains the device for this session
198
+ for (const herd_module of state.herd_modules) {
199
+ const device = herd_module.devices.find((d) => d.id === session.device_id);
200
+ if (device) {
201
+ herd_module.sessions = [session, ...herd_module.sessions];
202
+ return;
203
+ }
204
+ }
205
+ },
206
+ deleteSessionFromStore: (state, action) => {
207
+ const sessionId = action.payload.id;
208
+ for (const herd_module of state.herd_modules) {
209
+ herd_module.sessions = herd_module.sessions.filter((session) => session.id !== sessionId);
210
+ }
211
+ },
212
+ updateSessionInStore: (state, action) => {
213
+ const updatedSession = action.payload;
214
+ for (const herd_module of state.herd_modules) {
215
+ const sessionIndex = herd_module.sessions.findIndex((session) => session.id === updatedSession.id);
216
+ if (sessionIndex !== -1) {
217
+ herd_module.sessions[sessionIndex] = updatedSession;
218
+ return;
219
+ }
220
+ }
221
+ },
195
222
  setUser: (state, action) => {
196
223
  state.user = action.payload;
197
224
  },
198
225
  },
199
226
  });
200
227
  // Action creators are generated for each case reducer function
201
- export const { setHerdModules, setStatus, setActiveHerdId, setActiveDeviceId, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, } = scoutSlice.actions;
228
+ export const { setHerdModules, setStatus, setActiveHerdId, setActiveDeviceId, appendEventsToHerdModule, replaceEventsForHerdModule, updateEventValuesForHerdModule, updatePageIndexForHerdModule, appendPlansToHerdModule, setUser, addTag, deleteTag, updateTag, addNewDeviceToHerdModule, updateDeviceForHerdModule, addDevice, deleteDevice, updateDevice, addPlan, deletePlan, updatePlan, addSessionToStore, deleteSessionFromStore, updateSessionInStore, } = scoutSlice.actions;
202
229
  export default scoutSlice.reducer;
@@ -1,10 +1,11 @@
1
1
  import { SupabaseClient } from "@supabase/supabase-js";
2
- import { IDevice, IEventWithTags, IHerd, IPlan, IUserAndRole, IZoneWithActions } from "../types/db";
2
+ import { IDevice, IEventWithTags, IHerd, IPlan, IUserAndRole, IZoneWithActions, ISessionWithCoordinates } from "../types/db";
3
3
  export declare class HerdModule {
4
4
  herd: IHerd;
5
5
  devices: IDevice[];
6
6
  events: IEventWithTags[];
7
7
  zones: IZoneWithActions[];
8
+ sessions: ISessionWithCoordinates[];
8
9
  timestamp_last_refreshed: number;
9
10
  user_roles: IUserAndRole[] | null;
10
11
  events_page_index: number;
@@ -12,7 +13,7 @@ export declare class HerdModule {
12
13
  total_events_with_filters: number;
13
14
  labels: string[];
14
15
  plans: IPlan[];
15
- constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[]);
16
+ constructor(herd: IHerd, devices: IDevice[], events: IEventWithTags[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, events_page_index?: number, total_events?: number, total_events_with_filters?: number, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], sessions?: ISessionWithCoordinates[]);
16
17
  to_serializable(): IHerdModule;
17
18
  static from_herd(herd: IHerd, client: SupabaseClient): Promise<HerdModule>;
18
19
  }
@@ -28,4 +29,5 @@ export interface IHerdModule {
28
29
  labels: string[];
29
30
  plans: IPlan[];
30
31
  zones: IZoneWithActions[];
32
+ sessions: ISessionWithCoordinates[];
31
33
  }
@@ -1,14 +1,15 @@
1
1
  import { LABELS } from "../constants/annotator";
2
2
  import { get_devices_by_herd } from "../helpers/devices";
3
- import { server_get_total_events_by_herd } from "../helpers/events";
3
+ import { EnumSessionsVisibility, server_get_total_events_by_herd, } from "../helpers/events";
4
4
  import { server_get_plans_by_herd } from "../helpers/plans";
5
5
  import { server_get_events_and_tags_for_device } from "../helpers/tags";
6
6
  import { server_get_users_with_herd_access } from "../helpers/users";
7
7
  import { EnumWebResponse } from "./requests";
8
8
  import { server_get_more_zones_and_actions_for_herd } from "../helpers/zones";
9
9
  import { server_list_api_keys } from "../api_keys/actions";
10
+ import { getSessionsByHerdId } from "../helpers/sessions";
10
11
  export class HerdModule {
11
- constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = []) {
12
+ constructor(herd, devices, events, timestamp_last_refreshed, user_roles = null, events_page_index = 0, total_events = 0, total_events_with_filters = 0, labels = [], plans = [], zones = [], sessions = []) {
12
13
  this.user_roles = null;
13
14
  this.events_page_index = 0;
14
15
  this.total_events = 0;
@@ -26,6 +27,7 @@ export class HerdModule {
26
27
  this.labels = labels;
27
28
  this.plans = plans;
28
29
  this.zones = zones;
30
+ this.sessions = sessions;
29
31
  }
30
32
  to_serializable() {
31
33
  return {
@@ -40,43 +42,103 @@ export class HerdModule {
40
42
  labels: this.labels,
41
43
  plans: this.plans,
42
44
  zones: this.zones,
45
+ sessions: this.sessions,
43
46
  };
44
47
  }
45
48
  static async from_herd(herd, client) {
46
- // load devices
47
- let response_new_devices = await get_devices_by_herd(herd.id, client);
48
- if (response_new_devices.status == EnumWebResponse.ERROR ||
49
- !response_new_devices.data) {
50
- console.warn("No devices found for herd");
51
- return new HerdModule(herd, [], [], Date.now());
52
- }
53
- const new_devices = response_new_devices.data;
54
- // get api keys for each device... run requests in parallel
55
- if (new_devices.length > 0) {
56
- let api_keys_promises = new_devices.map((device) => server_list_api_keys(device.id?.toString() ?? ""));
57
- let api_keys = await Promise.all(api_keys_promises);
58
- for (let i = 0; i < new_devices.length; i++) {
59
- new_devices[i].api_keys_scout = api_keys[i];
49
+ try {
50
+ // load devices
51
+ let response_new_devices = await get_devices_by_herd(herd.id, client);
52
+ if (response_new_devices.status == EnumWebResponse.ERROR ||
53
+ !response_new_devices.data) {
54
+ console.warn("No devices found for herd");
55
+ return new HerdModule(herd, [], [], Date.now());
60
56
  }
61
- }
62
- // get recent events for each device... run requests in parallel
63
- let recent_events_promises = new_devices.map((device) => server_get_events_and_tags_for_device(device.id ?? 0));
64
- // Run all requests in parallel
65
- const [recent_events, res_zones, res_user_roles, total_event_count, res_plans,] = await Promise.all([
66
- Promise.all(recent_events_promises),
67
- server_get_more_zones_and_actions_for_herd(herd.id, 0, 10),
68
- server_get_users_with_herd_access(herd.id),
69
- server_get_total_events_by_herd(herd.id),
70
- server_get_plans_by_herd(herd.id),
71
- ]);
72
- for (let i = 0; i < new_devices.length; i++) {
73
- let x = recent_events[i].data;
74
- if (recent_events[i].status == EnumWebResponse.SUCCESS && x) {
75
- new_devices[i].recent_events = x;
57
+ const new_devices = response_new_devices.data;
58
+ // get api keys for each device... run requests in parallel
59
+ if (new_devices.length > 0) {
60
+ try {
61
+ let api_keys_promises = new_devices.map((device) => server_list_api_keys(device.id?.toString() ?? "").catch((error) => {
62
+ console.warn(`Failed to get API keys for device ${device.id}:`, error);
63
+ return undefined;
64
+ }));
65
+ let api_keys = await Promise.all(api_keys_promises);
66
+ for (let i = 0; i < new_devices.length; i++) {
67
+ new_devices[i].api_keys_scout = api_keys[i];
68
+ }
69
+ }
70
+ catch (error) {
71
+ console.warn("Failed to load API keys for devices:", error);
72
+ // Continue without API keys
73
+ }
76
74
  }
75
+ // get recent events for each device... run requests in parallel
76
+ let recent_events_promises = new_devices.map((device) => server_get_events_and_tags_for_device(device.id ?? 0).catch((error) => {
77
+ console.warn(`Failed to get events for device ${device.id}:`, error);
78
+ return { status: EnumWebResponse.ERROR, data: null };
79
+ }));
80
+ // Run all requests in parallel with individual error handling
81
+ const [recent_events, res_zones, res_user_roles, total_event_count, res_plans, res_sessions,] = await Promise.allSettled([
82
+ Promise.all(recent_events_promises),
83
+ server_get_more_zones_and_actions_for_herd(herd.id, 0, 10).catch((error) => {
84
+ console.warn("Failed to get zones and actions:", error);
85
+ return { status: EnumWebResponse.ERROR, data: null };
86
+ }),
87
+ server_get_users_with_herd_access(herd.id).catch((error) => {
88
+ console.warn("Failed to get user roles:", error);
89
+ return { status: EnumWebResponse.ERROR, data: null };
90
+ }),
91
+ server_get_total_events_by_herd(herd.id, EnumSessionsVisibility.Exclude).catch((error) => {
92
+ console.warn("Failed to get total events count:", error);
93
+ return { status: EnumWebResponse.ERROR, data: null };
94
+ }),
95
+ server_get_plans_by_herd(herd.id).catch((error) => {
96
+ console.warn("Failed to get plans:", error);
97
+ return { status: EnumWebResponse.ERROR, data: null };
98
+ }),
99
+ getSessionsByHerdId(client, herd.id).catch((error) => {
100
+ console.warn("Failed to get sessions:", error);
101
+ return [];
102
+ }),
103
+ ]);
104
+ // Process recent events with error handling
105
+ if (recent_events.status === "fulfilled") {
106
+ for (let i = 0; i < new_devices.length; i++) {
107
+ try {
108
+ let x = recent_events.value[i]?.data;
109
+ if (recent_events.value[i]?.status == EnumWebResponse.SUCCESS &&
110
+ x) {
111
+ new_devices[i].recent_events = x;
112
+ }
113
+ }
114
+ catch (error) {
115
+ console.warn(`Failed to process events for device ${new_devices[i].id}:`, error);
116
+ }
117
+ }
118
+ }
119
+ // Extract data with safe fallbacks
120
+ const zones = res_zones.status === "fulfilled" && res_zones.value?.data
121
+ ? res_zones.value.data
122
+ : [];
123
+ const user_roles = res_user_roles.status === "fulfilled" && res_user_roles.value?.data
124
+ ? res_user_roles.value.data
125
+ : null;
126
+ const total_events = total_event_count.status === "fulfilled" &&
127
+ total_event_count.value?.data
128
+ ? total_event_count.value.data
129
+ : 0;
130
+ const plans = res_plans.status === "fulfilled" && res_plans.value?.data
131
+ ? res_plans.value.data
132
+ : [];
133
+ const sessions = res_sessions.status === "fulfilled" ? res_sessions.value : [];
134
+ // TODO: store in DB and retrieve on load?
135
+ const newLabels = LABELS;
136
+ return new HerdModule(herd, new_devices, [], Date.now(), user_roles, 0, total_events, total_events, newLabels, plans, zones, sessions);
137
+ }
138
+ catch (error) {
139
+ console.error("Critical error in HerdModule.from_herd:", error);
140
+ // Return a minimal but valid HerdModule instance to prevent complete failure
141
+ return new HerdModule(herd, [], [], Date.now());
77
142
  }
78
- // TODO: store in DB and retrieve on load?
79
- const newLabels = LABELS;
80
- return new HerdModule(herd, new_devices, [], Date.now(), res_user_roles.data, 0, total_event_count.data ? total_event_count.data : 0, total_event_count.data ? total_event_count.data : 0, newLabels, res_plans.data ? res_plans.data : [], res_zones.data ? res_zones.data : []);
81
143
  }
82
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
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",