mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
				synced 2025-10-31 02:38:01 +00:00 
			
		
		
		
	[WIFI-10515] Crash fix when receiving corrupted statistics
Signed-off-by: Charles <charles.bourque96@gmail.com>
This commit is contained in:
		
							
								
								
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +1,12 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "2.7.0(0)", | ||||
|   "version": "2.7.0(1)", | ||||
|   "lockfileVersion": 2, | ||||
|   "requires": true, | ||||
|   "packages": { | ||||
|     "": { | ||||
|       "name": "ucentral-client", | ||||
|       "version": "2.7.0(0)", | ||||
|       "version": "2.7.0(1)", | ||||
|       "dependencies": { | ||||
|         "@coreui/coreui": "^3.4.0", | ||||
|         "@coreui/icons": "^2.0.1", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "ucentral-client", | ||||
|   "version": "2.7.0(0)", | ||||
|   "version": "2.7.0(1)", | ||||
|   "dependencies": { | ||||
|     "@coreui/coreui": "^3.4.0", | ||||
|     "@coreui/icons": "^2.0.1", | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import React, { useState, useEffect, useMemo } from 'react'; | ||||
| import { CButton, CModal, CModalHeader, CModalBody, CModalTitle, CPopover } from '@coreui/react'; | ||||
| import CIcon from '@coreui/icons-react'; | ||||
| import { cilX } from '@coreui/icons'; | ||||
| @@ -32,6 +32,17 @@ const LatestStatisticsModal = ({ show, toggle }) => { | ||||
|       .catch(() => {}); | ||||
|   }; | ||||
|  | ||||
|   const latestStatsString = useMemo(() => { | ||||
|     if (latestStats) { | ||||
|       try { | ||||
|         return JSON.stringify(latestStats, null, 2); | ||||
|       } catch (e) { | ||||
|         return ''; | ||||
|       } | ||||
|     } | ||||
|     return ''; | ||||
|   }, [latestStats]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (show) { | ||||
|       getLatestStats(); | ||||
| @@ -52,13 +63,9 @@ const LatestStatisticsModal = ({ show, toggle }) => { | ||||
|       </CModalHeader> | ||||
|       <CModalBody> | ||||
|         <div style={{ textAlign: 'right' }}> | ||||
|           <CopyToClipboardButton | ||||
|             t={t} | ||||
|             size="lg" | ||||
|             content={JSON.stringify(latestStats ?? {}, null, 4)} | ||||
|           /> | ||||
|           <CopyToClipboardButton t={t} size="lg" content={latestStatsString} /> | ||||
|         </div> | ||||
|         <pre className="ignore">{JSON.stringify(latestStats, null, 2)}</pre> | ||||
|         <pre className="ignore">{latestStatsString}</pre> | ||||
|       </CModalBody> | ||||
|     </CModal> | ||||
|   ); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import React, { useState, useEffect, useCallback } from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { CSpinner } from '@coreui/react'; | ||||
| import { CSpinner, CAlert } from '@coreui/react'; | ||||
| import { useTranslation } from 'react-i18next'; | ||||
| import { v4 as createUuid } from 'uuid'; | ||||
| import axiosInstance from 'utils/axiosInstance'; | ||||
| @@ -23,230 +23,243 @@ const StatisticsChartList = ({ deviceSerialNumber, setOptions, section, time }) | ||||
|     memory: [], | ||||
|     settings: {}, | ||||
|   }); | ||||
|   const [error, setError] = useState(false); | ||||
|  | ||||
|   const transformIntoDataset = (data) => { | ||||
|     let sortedData = data.sort((a, b) => { | ||||
|       if (a.recorded > b.recorded) return 1; | ||||
|       if (b.recorded > a.recorded) return -1; | ||||
|       return 0; | ||||
|     }); | ||||
|     try { | ||||
|       let sortedData = data.sort((a, b) => { | ||||
|         if (a.recorded > b.recorded) return 1; | ||||
|         if (b.recorded > a.recorded) return -1; | ||||
|         return 0; | ||||
|       }); | ||||
|  | ||||
|     const dataLength = sortedData.length; | ||||
|     if (dataLength > 1000 && dataLength < 3000) { | ||||
|       sortedData = sortedData.filter((dat, index) => index % 4 === 0); | ||||
|     } else if (dataLength >= 3000 && dataLength < 5000) { | ||||
|       sortedData = sortedData.filter((dat, index) => index % 8 === 0); | ||||
|     } else if (dataLength >= 5000 && dataLength < 7000) { | ||||
|       sortedData = sortedData.filter((dat, index) => index % 12 === 0); | ||||
|     } else if (dataLength > 7000) { | ||||
|       sortedData = sortedData.filter((dat, index) => index % 20 === 0); | ||||
|     } | ||||
|       const dataLength = sortedData.length; | ||||
|       if (dataLength > 1000 && dataLength < 3000) { | ||||
|         sortedData = sortedData.filter((dat, index) => index % 4 === 0); | ||||
|       } else if (dataLength >= 3000 && dataLength < 5000) { | ||||
|         sortedData = sortedData.filter((dat, index) => index % 8 === 0); | ||||
|       } else if (dataLength >= 5000 && dataLength < 7000) { | ||||
|         sortedData = sortedData.filter((dat, index) => index % 12 === 0); | ||||
|       } else if (dataLength > 7000) { | ||||
|         sortedData = sortedData.filter((dat, index) => index % 20 === 0); | ||||
|       } | ||||
|  | ||||
|     // Looping through data to build our memory graph data | ||||
|     const memoryUsed = [ | ||||
|       { | ||||
|         titleName: t('statistics.memory'), | ||||
|         name: 'Used', | ||||
|         backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|         data: [], | ||||
|         fill: true, | ||||
|       }, | ||||
|       { | ||||
|         titleName: t('statistics.memory'), | ||||
|         name: 'Buffered', | ||||
|         backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|         data: [], | ||||
|         fill: true, | ||||
|       }, | ||||
|       { | ||||
|         titleName: t('statistics.memory'), | ||||
|         name: 'Cached', | ||||
|         backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|         data: [], | ||||
|         fill: true, | ||||
|       }, | ||||
|     ]; | ||||
|       // Looping through data to build our memory graph data | ||||
|       const memoryUsed = [ | ||||
|         { | ||||
|           titleName: t('statistics.memory'), | ||||
|           name: 'Used', | ||||
|           backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|           data: [], | ||||
|           fill: true, | ||||
|         }, | ||||
|         { | ||||
|           titleName: t('statistics.memory'), | ||||
|           name: 'Buffered', | ||||
|           backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|           data: [], | ||||
|           fill: true, | ||||
|         }, | ||||
|         { | ||||
|           titleName: t('statistics.memory'), | ||||
|           name: 'Cached', | ||||
|           backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|           data: [], | ||||
|           fill: true, | ||||
|         }, | ||||
|       ]; | ||||
|  | ||||
|     for (const log of sortedData) { | ||||
|       memoryUsed[0].data.push( | ||||
|         Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024), | ||||
|       for (const log of sortedData) { | ||||
|         memoryUsed[0].data.push( | ||||
|           Math.floor((log.data.unit.memory.total - log.data.unit.memory.free) / 1024 / 1024), | ||||
|         ); | ||||
|         memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024)); | ||||
|         memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024)); | ||||
|       } | ||||
|  | ||||
|       const newUsed = memoryUsed[0].data; | ||||
|       if (newUsed.length > 0) newUsed.shift(); | ||||
|       memoryUsed[0].data = newUsed; | ||||
|       const newBuff = memoryUsed[1].data; | ||||
|       if (newBuff.length > 0) newBuff.shift(); | ||||
|       memoryUsed[1].data = newBuff; | ||||
|       const newCached = memoryUsed[2].data; | ||||
|       if (newCached.length > 0) newCached.shift(); | ||||
|       memoryUsed[2].data = newCached; | ||||
|  | ||||
|       // This dictionary will have a key that is the interface name and a value of it's index in the final array | ||||
|       const interfaceTypes = {}; | ||||
|       const interfaceList = []; | ||||
|       const categories = []; | ||||
|       let i = 0; | ||||
|       const areSameDay = datesSameDay( | ||||
|         new Date(sortedData[0].recorded * 1000), | ||||
|         new Date(sortedData[sortedData.length - 1].recorded * 1000), | ||||
|       ); | ||||
|       memoryUsed[1].data.push(Math.floor(log.data.unit.memory.buffered / 1024 / 1024)); | ||||
|       memoryUsed[2].data.push(Math.floor(log.data.unit.memory.cached / 1024 / 1024)); | ||||
|     } | ||||
|  | ||||
|     const newUsed = memoryUsed[0].data; | ||||
|     if (newUsed.length > 0) newUsed.shift(); | ||||
|     memoryUsed[0].data = newUsed; | ||||
|     const newBuff = memoryUsed[1].data; | ||||
|     if (newBuff.length > 0) newBuff.shift(); | ||||
|     memoryUsed[1].data = newBuff; | ||||
|     const newCached = memoryUsed[2].data; | ||||
|     if (newCached.length > 0) newCached.shift(); | ||||
|     memoryUsed[2].data = newCached; | ||||
|  | ||||
|     // This dictionary will have a key that is the interface name and a value of it's index in the final array | ||||
|     const interfaceTypes = {}; | ||||
|     const interfaceList = []; | ||||
|     const categories = []; | ||||
|     let i = 0; | ||||
|     const areSameDay = datesSameDay( | ||||
|       new Date(sortedData[0].recorded * 1000), | ||||
|       new Date(sortedData[sortedData.length - 1].recorded * 1000), | ||||
|     ); | ||||
|  | ||||
|     // Just building the array for all the interfaces | ||||
|     for (const log of sortedData) { | ||||
|       categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded)); | ||||
|       for (const logInterface of log.data.interfaces) { | ||||
|         if (interfaceTypes[logInterface.name] === undefined) { | ||||
|           interfaceTypes[logInterface.name] = i; | ||||
|           interfaceList.push([ | ||||
|             { | ||||
|               titleName: logInterface.name, | ||||
|               name: 'Tx', | ||||
|               backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|               data: [], | ||||
|               fill: false, | ||||
|             }, | ||||
|             { | ||||
|               titleName: logInterface.name, | ||||
|               name: 'Rx', | ||||
|               backgroundColor: 'rgb(0,216,255,0.9)', | ||||
|               data: [], | ||||
|               fill: false, | ||||
|             }, | ||||
|           ]); | ||||
|           i += 1; | ||||
|       // Just building the array for all the interfaces | ||||
|       for (const log of sortedData) { | ||||
|         categories.push(areSameDay ? unixToTime(log.recorded) : prettyDate(log.recorded)); | ||||
|         for (const logInterface of log.data.interfaces) { | ||||
|           if (interfaceTypes[logInterface.name] === undefined) { | ||||
|             interfaceTypes[logInterface.name] = i; | ||||
|             interfaceList.push([ | ||||
|               { | ||||
|                 titleName: logInterface.name, | ||||
|                 name: 'Tx', | ||||
|                 backgroundColor: 'rgb(228,102,81,0.9)', | ||||
|                 data: [], | ||||
|                 fill: false, | ||||
|               }, | ||||
|               { | ||||
|                 titleName: logInterface.name, | ||||
|                 name: 'Rx', | ||||
|                 backgroundColor: 'rgb(0,216,255,0.9)', | ||||
|                 data: [], | ||||
|                 fill: false, | ||||
|               }, | ||||
|             ]); | ||||
|             i += 1; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Looping through all the data | ||||
|     const prevTxObj = {}; | ||||
|     const prevRxObj = {}; | ||||
|     for (const log of sortedData) { | ||||
|       // Looping through the interfaces of the log | ||||
|       const version = log.data.version ?? 0; | ||||
|       for (const inter of log.data.interfaces) { | ||||
|         if (version > 0) { | ||||
|           const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0; | ||||
|           const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0; | ||||
|           const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0; | ||||
|           const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0; | ||||
|           interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx)); | ||||
|           interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx)); | ||||
|           prevTxObj[inter.name] = tx; | ||||
|           prevRxObj[inter.name] = rx; | ||||
|         } else { | ||||
|           interfaceList[interfaceTypes[inter.name]][0].data.push( | ||||
|             inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0, | ||||
|           ); | ||||
|           interfaceList[interfaceTypes[inter.name]][1].data.push( | ||||
|             inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0, | ||||
|           ); | ||||
|       // Looping through all the data | ||||
|       const prevTxObj = {}; | ||||
|       const prevRxObj = {}; | ||||
|       for (const log of sortedData) { | ||||
|         // Looping through the interfaces of the log | ||||
|         const version = log.data.version ?? 0; | ||||
|         for (const inter of log.data.interfaces) { | ||||
|           if (version > 0) { | ||||
|             const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0; | ||||
|             const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0; | ||||
|             const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0; | ||||
|             const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0; | ||||
|             interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx)); | ||||
|             interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx)); | ||||
|             prevTxObj[inter.name] = tx; | ||||
|             prevRxObj[inter.name] = rx; | ||||
|           } else { | ||||
|             interfaceList[interfaceTypes[inter.name]][0].data.push( | ||||
|               inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0, | ||||
|             ); | ||||
|             interfaceList[interfaceTypes[inter.name]][1].data.push( | ||||
|               inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0, | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     for (let y = 0; y < interfaceList.length; y += 1) { | ||||
|       for (let z = 0; z < interfaceList[y].length; z += 1) { | ||||
|         const newArray = interfaceList[y][z].data; | ||||
|         if (newArray.length > 0) newArray.shift(); | ||||
|         interfaceList[y][z].data = newArray; | ||||
|       for (let y = 0; y < interfaceList.length; y += 1) { | ||||
|         for (let z = 0; z < interfaceList[y].length; z += 1) { | ||||
|           const newArray = interfaceList[y][z].data; | ||||
|           if (newArray.length > 0) newArray.shift(); | ||||
|           interfaceList[y][z].data = newArray; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     const newCategories = categories; | ||||
|     if (newCategories.length > 0) newCategories.shift(); | ||||
|     const interfaceOptions = { | ||||
|       chart: { | ||||
|         id: 'chart', | ||||
|       }, | ||||
|       stroke: { | ||||
|         curve: 'smooth', | ||||
|       }, | ||||
|       xaxis: { | ||||
|         title: { | ||||
|           text: 'Time', | ||||
|           style: { | ||||
|             fontSize: '15px', | ||||
|       const newCategories = categories; | ||||
|       if (newCategories.length > 0) newCategories.shift(); | ||||
|       const interfaceOptions = { | ||||
|         chart: { | ||||
|           id: 'chart', | ||||
|         }, | ||||
|         stroke: { | ||||
|           curve: 'smooth', | ||||
|         }, | ||||
|         xaxis: { | ||||
|           title: { | ||||
|             text: 'Time', | ||||
|             style: { | ||||
|               fontSize: '15px', | ||||
|             }, | ||||
|           }, | ||||
|           categories: newCategories, | ||||
|           tickAmount: areSameDay ? 15 : 10, | ||||
|         }, | ||||
|         yaxis: { | ||||
|           labels: { | ||||
|             minWidth: 40, | ||||
|           }, | ||||
|           title: { | ||||
|             text: t('statistics.data'), | ||||
|             style: { | ||||
|               fontSize: '15px', | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|         categories: newCategories, | ||||
|         tickAmount: areSameDay ? 15 : 10, | ||||
|       }, | ||||
|       yaxis: { | ||||
|         labels: { | ||||
|           minWidth: 40, | ||||
|         legend: { | ||||
|           position: 'top', | ||||
|           horizontalAlign: 'right', | ||||
|           float: true, | ||||
|         }, | ||||
|         title: { | ||||
|           text: t('statistics.data'), | ||||
|           style: { | ||||
|             fontSize: '15px', | ||||
|       }; | ||||
|  | ||||
|       const memoryOptions = { | ||||
|         chart: { | ||||
|           id: 'chart', | ||||
|         }, | ||||
|         stroke: { | ||||
|           curve: 'smooth', | ||||
|         }, | ||||
|         xaxis: { | ||||
|           tickAmount: areSameDay ? 15 : 10, | ||||
|           title: { | ||||
|             text: 'Time', | ||||
|             style: { | ||||
|               fontSize: '15px', | ||||
|             }, | ||||
|           }, | ||||
|           categories, | ||||
|         }, | ||||
|         yaxis: { | ||||
|           tickAmount: 5, | ||||
|           title: { | ||||
|             text: t('statistics.data_mb'), | ||||
|             style: { | ||||
|               fontSize: '15px', | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       legend: { | ||||
|         position: 'top', | ||||
|         horizontalAlign: 'right', | ||||
|         float: true, | ||||
|       }, | ||||
|     }; | ||||
|  | ||||
|     const memoryOptions = { | ||||
|       chart: { | ||||
|         id: 'chart', | ||||
|       }, | ||||
|       stroke: { | ||||
|         curve: 'smooth', | ||||
|       }, | ||||
|       xaxis: { | ||||
|         tickAmount: areSameDay ? 15 : 10, | ||||
|         title: { | ||||
|           text: 'Time', | ||||
|           style: { | ||||
|             fontSize: '15px', | ||||
|           }, | ||||
|         legend: { | ||||
|           position: 'top', | ||||
|           horizontalAlign: 'right', | ||||
|           float: true, | ||||
|         }, | ||||
|         categories, | ||||
|       }, | ||||
|       yaxis: { | ||||
|         tickAmount: 5, | ||||
|         title: { | ||||
|           text: t('statistics.data_mb'), | ||||
|           style: { | ||||
|             fontSize: '15px', | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       legend: { | ||||
|         position: 'top', | ||||
|         horizontalAlign: 'right', | ||||
|         float: true, | ||||
|       }, | ||||
|     }; | ||||
|       }; | ||||
|  | ||||
|     const newOptions = { | ||||
|       interfaceList, | ||||
|       memory: [memoryUsed], | ||||
|       interfaceOptions, | ||||
|       memoryOptions, | ||||
|       start: new Date(sortedData[0].recorded * 1000).toISOString(), | ||||
|       end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(), | ||||
|     }; | ||||
|       const newOptions = { | ||||
|         interfaceList, | ||||
|         memory: [memoryUsed], | ||||
|         interfaceOptions, | ||||
|         memoryOptions, | ||||
|         start: new Date(sortedData[0].recorded * 1000).toISOString(), | ||||
|         end: new Date(sortedData[sortedData.length - 1].recorded * 1000).toISOString(), | ||||
|       }; | ||||
|  | ||||
|     if (statOptions !== newOptions) { | ||||
|       const sectionOptions = newOptions.interfaceList.map((opt) => ({ | ||||
|         value: opt[0].titleName, | ||||
|         label: opt[0].titleName, | ||||
|       })); | ||||
|       setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]); | ||||
|       setStatOptions({ ...newOptions }); | ||||
|       if (statOptions !== newOptions) { | ||||
|         const sectionOptions = newOptions.interfaceList.map((opt) => ({ | ||||
|           value: opt[0].titleName, | ||||
|           label: opt[0].titleName, | ||||
|         })); | ||||
|         setOptions([...sectionOptions, { value: 'memory', label: t('statistics.memory') }]); | ||||
|         setStatOptions({ ...newOptions }); | ||||
|       } | ||||
|       setError(false); | ||||
|     } catch (e) { | ||||
|       setError(true); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const getInterface = useCallback(() => { | ||||
|     if (error) { | ||||
|       return ( | ||||
|         <CAlert color="danger" style={{ width: '240px' }}> | ||||
|           Error while parsing statistics | ||||
|         </CAlert> | ||||
|       ); | ||||
|     } | ||||
|     if (statOptions.interfaceList.length === 0) return <p>N/A</p>; | ||||
|  | ||||
|     const interfaceToShow = statOptions.interfaceList.find( | ||||
| @@ -273,8 +286,9 @@ const StatisticsChartList = ({ deviceSerialNumber, setOptions, section, time }) | ||||
|         </div> | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return <p>N/A</p>; | ||||
|   }, [statOptions, section]); | ||||
|   }, [statOptions, section, error]); | ||||
|  | ||||
|   const getStatistics = () => { | ||||
|     setLoading(true); | ||||
|   | ||||
| @@ -31,25 +31,3 @@ export const extractWebSocketResponse = (message) => { | ||||
|   } | ||||
|   return undefined; | ||||
| }; | ||||
|  | ||||
| export const getStatusFromNotification = (notification) => { | ||||
|   let status = 'success'; | ||||
|   if (notification.content.warning?.length > 0) status = 'warning'; | ||||
|   if (notification.content.error?.length > 0) status = 'error'; | ||||
|  | ||||
|   return status; | ||||
| }; | ||||
|  | ||||
| export const getNotificationDescription = (t, notification) => { | ||||
|   if ( | ||||
|     notification.content.type === 'venue_configuration_update' || | ||||
|     notification.content.type === 'entity_configuration_update' | ||||
|   ) { | ||||
|     return t('configurations.notification_details', { | ||||
|       success: notification.content.success.length, | ||||
|       warning: notification.content.warning.length, | ||||
|       error: notification.content.error.length, | ||||
|     }); | ||||
|   } | ||||
|   return notification.content.details; | ||||
| }; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Charles
					Charles