diff --git a/src/components/Dashboard/common/ObjectDisplay.jsx b/src/components/Dashboard/common/ObjectDisplay.jsx
index d0722dc..71b215a 100644
--- a/src/components/Dashboard/common/ObjectDisplay.jsx
+++ b/src/components/Dashboard/common/ObjectDisplay.jsx
@@ -1,19 +1,61 @@
import PropTypes from 'prop-types'
-import { Tag, Typography } from 'antd'
+import { Typography, Flex } from 'antd'
+import { useState, useEffect, useContext, useCallback } from 'react'
import { getModelByName } from '../../../database/ObjectModels'
+import { ApiServerContext } from '../context/ApiServerContext'
+import { AuthContext } from '../context/AuthContext'
+import merge from 'lodash/merge'
const { Text } = Typography
const ObjectDisplay = ({ object, objectType }) => {
- if (!object) {
+ const [objectData, setObjectData] = useState(object)
+ const { subscribeToObjectUpdates, connected } = useContext(ApiServerContext)
+ const { token } = useContext(AuthContext)
+
+ // Update event handler
+ const updateObjectEventHandler = useCallback((value) => {
+ setObjectData((prev) => merge({}, prev, value))
+ }, [])
+
+ // Subscribe to object updates when component mounts
+ useEffect(() => {
+ if (object?._id && objectType && connected && token) {
+ const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
+ object._id,
+ objectType,
+ updateObjectEventHandler
+ )
+
+ return () => {
+ if (objectUpdatesUnsubscribe) objectUpdatesUnsubscribe()
+ }
+ }
+ }, [
+ object?._id,
+ objectType,
+ subscribeToObjectUpdates,
+ connected,
+ token,
+ updateObjectEventHandler
+ ])
+
+ // Update local state when object prop changes
+ useEffect(() => {
+ setObjectData(object)
+ }, [object])
+
+ if (!objectData) {
return n/a
}
+
const model = getModelByName(objectType)
const Icon = model.icon
return (
- }>
- {object?.name ? object.name : null}
-
+
+
+ {objectData?.name ? objectData.name : null}
+
)
}
diff --git a/src/components/Dashboard/common/ObjectTable.jsx b/src/components/Dashboard/common/ObjectTable.jsx
index 84d4896..fca65c4 100644
--- a/src/components/Dashboard/common/ObjectTable.jsx
+++ b/src/components/Dashboard/common/ObjectTable.jsx
@@ -87,8 +87,10 @@ const ObjectTable = forwardRef(
const [loading, setLoading] = useState(true)
const [lazyLoading, setLazyLoading] = useState(false)
- const [subscribedIds, setSubscribedIds] = useState([])
+ const subscribedIdsRef = useRef([])
const [typeSubscribed, setTypeSubscribed] = useState(false)
+ const unsubscribesRef = useRef([])
+ const updateEventHandlerRef = useRef()
const rowActions =
model.actions?.filter((action) => action.row == true) || []
@@ -287,6 +289,7 @@ const ObjectTable = forwardRef(
// Update event handler for real-time updates
const updateEventHandler = useCallback((id, updatedData) => {
+ console.log('GOT UPDATE FOR', id)
setPages((prevPages) =>
prevPages.map((page) => ({
...page,
@@ -297,6 +300,9 @@ const ObjectTable = forwardRef(
)
}, [])
+ // Store the latest updateEventHandler in a ref
+ updateEventHandlerRef.current = updateEventHandler
+
const newEventHandler = useCallback(() => {
console.log('GOT NEW EVENT')
reload()
@@ -304,44 +310,68 @@ const ObjectTable = forwardRef(
// Subscribe to real-time updates for all items
useEffect(() => {
- if (pages.length > 0 && connected) {
- const unsubscribes = []
- // Subscribe to each item in all pages
- pages.forEach((page) => {
- if (page?.items && page?.items?.length > 0) {
- page.items.forEach((item) => {
- if (!item.isSkeleton && !subscribedIds.includes(item?._id)) {
- const unsubscribe = subscribeToObjectUpdates(
- item._id,
- type,
- (updateData) => {
- updateEventHandler(item._id, updateData)
- }
- )
- setSubscribedIds((prev) => [...prev, item._id])
- if (unsubscribe) {
- unsubscribes.push(unsubscribe)
- }
- }
- })
+ if (pages.length > 0 && connected == true) {
+ // Get all non-skeleton item IDs from all pages
+ const allItemIds = pages
+ .flatMap((page) => page.items || [])
+ .filter((item) => !item.isSkeleton)
+ .map((item) => item._id)
+ .filter(Boolean)
+
+ // Find new items that need subscription
+ const newItemIds = allItemIds.filter(
+ (id) => !subscribedIdsRef.current.includes(id)
+ )
+
+ // Subscribe to new items only
+ newItemIds.forEach((itemId) => {
+ console.log('SUB', itemId)
+ const unsubscribe = subscribeToObjectUpdates(
+ itemId,
+ type,
+ (updateData) => {
+ updateEventHandlerRef.current(itemId, updateData)
+ }
+ )
+ subscribedIdsRef.current.push(itemId)
+ if (unsubscribe) {
+ unsubscribesRef.current.push(unsubscribe)
}
})
- return () => {
- // Clean up all subscriptions
- unsubscribes.forEach((unsubscribe) => {
- if (unsubscribe) unsubscribe()
- })
- }
+ // Clean up subscriptions for items that are no longer in the pages
+ const currentSubscribedIds = subscribedIdsRef.current
+ const itemsToUnsubscribe = currentSubscribedIds.filter(
+ (id) => !allItemIds.includes(id)
+ )
+
+ itemsToUnsubscribe.forEach((itemId) => {
+ const index = subscribedIdsRef.current.indexOf(itemId)
+ if (index > -1) {
+ subscribedIdsRef.current.splice(index, 1)
+ const unsubscribe = unsubscribesRef.current[index]
+ if (unsubscribe) {
+ console.log('UNSUB', itemId)
+ unsubscribe()
+ }
+ unsubscribesRef.current.splice(index, 1)
+ }
+ })
}
- }, [
- pages,
- type,
- subscribeToObjectUpdates,
- updateEventHandler,
- connected,
- subscribedIds
- ])
+ }, [pages, type, subscribeToObjectUpdates, connected])
+
+ // Cleanup effect for component unmount
+ useEffect(() => {
+ return () => {
+ // Clean up all subscriptions when component unmounts
+ unsubscribesRef.current.forEach((unsubscribe) => {
+ console.log('CALLING UNSUB on unmount')
+ if (unsubscribe) unsubscribe()
+ })
+ unsubscribesRef.current = []
+ subscribedIdsRef.current = []
+ }
+ }, [])
useEffect(() => {
if (connected == true && typeSubscribed == false) {
diff --git a/src/components/Dashboard/common/SpotlightTooltip.jsx b/src/components/Dashboard/common/SpotlightTooltip.jsx
index d5430c9..43ccfa1 100644
--- a/src/components/Dashboard/common/SpotlightTooltip.jsx
+++ b/src/components/Dashboard/common/SpotlightTooltip.jsx
@@ -7,6 +7,8 @@ import { AuthContext } from '../context/AuthContext'
import ObjectProperty from './ObjectProperty'
import InfoCircleIcon from '../../Icons/InfoCircleIcon'
import { ApiServerContext } from '../context/ApiServerContext'
+import merge from 'lodash/merge'
+import { getModelByName } from '../../../database/ObjectModels'
const { Text } = Typography
@@ -16,7 +18,17 @@ const SpotlightTooltip = ({ query, type }) => {
const [initialized, setInitialized] = useState(false)
const { token } = useContext(AuthContext)
- const { fetchSpotlightData } = useContext(ApiServerContext)
+ const { fetchSpotlightData, subscribeToObjectUpdates, connected } =
+ useContext(ApiServerContext)
+
+ // Get the model for this type
+ const model = getModelByName(type)
+ const modelProperties = model.properties || []
+
+ // Update event handler
+ const updateObjectEventHandler = useCallback((value) => {
+ setSpotlightData((prev) => merge({}, prev, value))
+ }, [])
const fetchSpotlight = useCallback(async () => {
setLoading(true)
@@ -33,6 +45,28 @@ const SpotlightTooltip = ({ query, type }) => {
}
}, [token, fetchSpotlight, initialized])
+ // Subscribe to object updates when component mounts
+ useEffect(() => {
+ if (spotlightData?._id && type && connected && token) {
+ const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
+ spotlightData._id,
+ type,
+ updateObjectEventHandler
+ )
+
+ return () => {
+ if (objectUpdatesUnsubscribe) objectUpdatesUnsubscribe()
+ }
+ }
+ }, [
+ spotlightData?._id,
+ type,
+ subscribeToObjectUpdates,
+ connected,
+ token,
+ updateObjectEventHandler
+ ])
+
if (!spotlightData && !loading) {
return (
@@ -51,29 +85,9 @@ const SpotlightTooltip = ({ query, type }) => {
)
}
- // Helper to determine property type based on key and value
- const getPropertyType = (key, value) => {
- if (key === '_id') {
- return 'id'
- }
- if (key === 'createdAt' || key === 'updatedAt') {
- return 'dateTime'
- }
- if (typeof value === 'boolean') {
- return 'bool'
- }
- return key
- }
-
- // Map of property names to user-friendly labels
- const LABEL_MAP = {
- name: 'Name',
- state: 'State',
- tags: 'Tags',
- email: 'Email',
- updatedAt: 'Updated At',
- _id: 'ID'
- // Add more mappings as needed
+ // Helper to get nested property value
+ const getNestedValue = (obj, path) => {
+ return path.split('.').reduce((current, key) => current?.[key], obj)
}
return (
@@ -105,33 +119,31 @@ const SpotlightTooltip = ({ query, type }) => {
>
) : (
- Object.entries(spotlightData).map(([key, value]) =>
- value !== undefined &&
- value !== null &&
- value !== '' &&
- key !== 'objectType' ? (
-
-
-
- ) : null
- )
+ modelProperties
+ .filter((prop) => {
+ const value = getNestedValue(spotlightData, prop.name)
+ return value !== undefined && value !== null && value !== ''
+ })
+ .map((prop) => {
+ const value = getNestedValue(spotlightData, prop.name)
+ return (
+
+
+
+ )
+ })
)}
diff --git a/src/components/Dashboard/context/ApiServerContext.jsx b/src/components/Dashboard/context/ApiServerContext.jsx
index 1009cfb..004bb43 100644
--- a/src/components/Dashboard/context/ApiServerContext.jsx
+++ b/src/components/Dashboard/context/ApiServerContext.jsx
@@ -285,20 +285,33 @@ const ApiServerProvider = ({ children }) => {
if (!subscribedCallbacksRef.current.has(callbacksRefKey)) {
subscribedCallbacksRef.current.set(callbacksRefKey, [])
}
- subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
- socketRef.current.emit(
- 'subscribeToObjectUpdate',
- {
- _id: id,
- objectType: objectType
- },
- (result) => {
- if (result.success) {
- logger.info('Subscribed to id:', id, 'objectType:', objectType)
+ const callbacksLength =
+ subscribedCallbacksRef.current.get(callbacksRefKey).length
+
+ if (callbacksLength <= 0) {
+ socketRef.current.emit(
+ 'subscribeToObjectUpdate',
+ {
+ _id: id,
+ objectType: objectType
+ },
+ (result) => {
+ if (result.success) {
+ logger.info('Subscribed to id:', id, 'objectType:', objectType)
+ }
}
- }
+ )
+ }
+ logger.info(
+ 'Adding callback id:',
+ id,
+ 'objectType:',
+ objectType,
+ 'callbacks length:',
+ callbacksLength + 1
)
+ subscribedCallbacksRef.current.get(callbacksRefKey).push(callback)
// Return cleanup function
return () => offObjectUpdatesEvent(id, objectType, callback)