1919 */
2020
2121import React , {
22+ Suspense ,
2223 useCallback ,
2324 useEffect ,
24- useRef ,
25+ useMemo ,
2526 useState ,
26- Suspense ,
2727} from "react" ;
2828import { ToggleButton , ToggleButtonGroup } from "react-bootstrap" ;
29- import type { Subscription } from "relay-runtime" ;
3029import { FormattedMessage , useIntl } from "react-intl" ;
30+ import type { PreloadedQuery } from "react-relay/hooks" ;
3131import {
32+ ConnectionHandler ,
3233 graphql ,
3334 useMutation ,
34- fetchQuery ,
35- useRelayEnvironment ,
3635 usePaginationFragment ,
3736 usePreloadedQuery ,
3837 useQueryLoader ,
38+ useSubscription ,
3939} from "react-relay/hooks" ;
40- import type { PreloadedQuery } from "react-relay/hooks" ;
4140
4241import type { SoftwareUpdateTab_createManualOtaOperation_Mutation } from "@/api/__generated__/SoftwareUpdateTab_createManualOtaOperation_Mutation.graphql" ;
4342import type { SoftwareUpdateTab_getBaseImageCollections_Query } from "@/api/__generated__/SoftwareUpdateTab_getBaseImageCollections_Query.graphql" ;
44- import type { SoftwareUpdateTab_PaginationQuery } from "@/api/__generated__/SoftwareUpdateTab_PaginationQuery.graphql" ;
45- import type { SoftwareUpdateTab_otaOperations$key } from "@/api/__generated__/SoftwareUpdateTab_otaOperations.graphql" ;
4643
4744import Alert from "@/components/Alert" ;
4845import OperationTable from "@/components/OperationTable" ;
@@ -52,37 +49,23 @@ import { Tab } from "@/components/Tabs";
5249import { RECORDS_TO_LOAD_FIRST } from "@/constants" ;
5350import ManualOtaFromCollectionForm from "@/forms/ManualOtaFromCollectionForm" ;
5451import ManualOtaFromFileForm from "@/forms/ManualOtaFromFileForm" ;
52+ import { SoftwareUpdateTab_otaOperations$key } from "@/api/__generated__/SoftwareUpdateTab_otaOperations.graphql" ;
53+ import { OtaOperations_PaginationQuery } from "@/api/__generated__/OtaOperations_PaginationQuery.graphql" ;
5554
55+ /* eslint-disable relay/unused-fields */
5656const DEVICE_OTA_OPERATIONS_FRAGMENT = graphql `
5757 fragment SoftwareUpdateTab_otaOperations on Device
58- @refetchable(queryName: "SoftwareUpdateTab_PaginationQuery ") {
58+ @refetchable(queryName: "OtaOperations_PaginationQuery ") {
5959 id
6060 capabilities
6161 otaOperations(first: $first, after: $after)
6262 @connection(key: "SoftwareUpdateTab_otaOperations") {
6363 edges {
6464 node {
65- id
66- baseImageUrl
67- status
68- createdAt
65+ __typename
6966 }
7067 }
71- }
72- ...OperationTable_otaOperations
73- }
74- ` ;
75-
76- const GET_DEVICE_OTA_OPERATIONS_QUERY = graphql `
77- query SoftwareUpdateTab_getDeviceOtaOperations_Query(
78- $id: ID!
79- $first: Int
80- $after: String
81- ) {
82- device(id: $id) {
83- id
84- ...Device_connectionStatus
85- ...SoftwareUpdateTab_otaOperations
68+ ...OperationTable_otaOperationEdgeFragment
8669 }
8770 }
8871` ;
@@ -97,6 +80,29 @@ const DEVICE_CREATE_MANUAL_OTA_OPERATION_MUTATION = graphql`
9780 baseImageUrl
9881 createdAt
9982 status
83+ statusProgress
84+ statusCode
85+ updatedAt
86+ campaignTarget {
87+ campaign {
88+ id
89+ name
90+ }
91+ }
92+ }
93+ }
94+ }
95+ ` ;
96+
97+ const OTA_OPERATION_UPDATED_SUBSCRIPTION = graphql `
98+ subscription SoftwareUpdateTab_otaOperation_updated_Subscription {
99+ otaOperation {
100+ updated {
101+ id
102+ baseImageUrl
103+ createdAt
104+ status
105+ statusProgress
100106 statusCode
101107 updatedAt
102108 }
@@ -152,15 +158,13 @@ type DeviceSoftwareUpdateTabProps = {
152158const DeviceSoftwareUpdateTab = ( {
153159 deviceRef,
154160} : DeviceSoftwareUpdateTabProps ) => {
155- const [ isRefreshing , setIsRefreshing ] = useState ( false ) ;
156161 const [ errorFeedback , setErrorFeedback ] = useState < React . ReactNode > ( null ) ;
157162 const intl = useIntl ( ) ;
158- const relayEnvironment = useRelayEnvironment ( ) ;
159163
160164 const [ updateMode , setUpdateMode ] = useState < "file" | "collection" > ( "file" ) ;
161165
162166 const { data } = usePaginationFragment <
163- SoftwareUpdateTab_PaginationQuery ,
167+ OtaOperations_PaginationQuery ,
164168 SoftwareUpdateTab_otaOperations$key
165169 > ( DEVICE_OTA_OPERATIONS_FRAGMENT , deviceRef ) ;
166170
@@ -171,74 +175,16 @@ const DeviceSoftwareUpdateTab = ({
171175 DEVICE_CREATE_MANUAL_OTA_OPERATION_MUTATION ,
172176 ) ;
173177
174- const otaOperations = (
175- data . otaOperations ?. edges ?. map ( ( { node } ) => node ) || [ ]
176- ) . sort ( ( a , b ) => {
177- if ( a . createdAt > b . createdAt ) {
178- return - 1 ;
179- }
180- if ( a . createdAt < b . createdAt ) {
181- return 1 ;
182- }
183- return 0 ;
184- } ) ;
185-
186- const lastFinishedOperationIndex = otaOperations . findIndex (
187- ( { status } ) => status === "SUCCESS" || status === "FAILURE" ,
178+ useSubscription (
179+ useMemo (
180+ ( ) => ( {
181+ subscription : OTA_OPERATION_UPDATED_SUBSCRIPTION ,
182+ variables : { deviceId } ,
183+ } ) ,
184+ [ deviceId ] ,
185+ ) ,
188186 ) ;
189187
190- const currentOperations =
191- lastFinishedOperationIndex === - 1
192- ? otaOperations
193- : otaOperations . slice ( 0 , lastFinishedOperationIndex ) ;
194-
195- // For now devices only support 1 update operation at a time
196- const currentOperation = currentOperations [ 0 ] || null ;
197-
198- // TODO: use GraphQL subscription (when available) to get updates about OTA operation
199- const subscriptionRef = useRef < Subscription | null > ( null ) ;
200- useEffect ( ( ) => {
201- return ( ) => {
202- if ( subscriptionRef . current ) {
203- subscriptionRef . current . unsubscribe ( ) ;
204- }
205- } ;
206- } , [ ] ) ;
207-
208- useEffect ( ( ) => {
209- if ( ! currentOperation || isRefreshing ) {
210- return ;
211- }
212- const refreshTimerId = setTimeout ( ( ) => {
213- setIsRefreshing ( true ) ;
214- subscriptionRef . current = fetchQuery (
215- relayEnvironment ,
216- GET_DEVICE_OTA_OPERATIONS_QUERY ,
217- {
218- id : deviceId ,
219- first : 10_000 ,
220- } ,
221- ) . subscribe ( {
222- complete : ( ) => {
223- setIsRefreshing ( false ) ;
224- } ,
225- error : ( ) => {
226- setIsRefreshing ( false ) ;
227- } ,
228- } ) ;
229- } , 10000 ) ;
230-
231- return ( ) => {
232- clearTimeout ( refreshTimerId ) ;
233- } ;
234- } , [
235- currentOperation ,
236- isRefreshing ,
237- setIsRefreshing ,
238- relayEnvironment ,
239- deviceId ,
240- ] ) ;
241-
242188 const [ getBaseImageCollsQuery , getBaseImageColls ] =
243189 useQueryLoader < SoftwareUpdateTab_getBaseImageCollections_Query > (
244190 GET_BASE_IMAGE_COLL_QUERY ,
@@ -290,22 +236,43 @@ const DeviceSoftwareUpdateTab = ({
290236 ) ;
291237 } ,
292238 updater ( store , data ) {
293- const otaOperationId = data ?. createManualOtaOperation ?. result ?. id ;
294- if ( otaOperationId ) {
295- const otaOperation = store . get ( otaOperationId ) ;
296- const storedDevice = store . get ( deviceId ) ;
297- const otaOperations = storedDevice ?. getLinkedRecords ( "otaOperations" ) ;
298- if ( storedDevice && otaOperation && otaOperations ) {
299- storedDevice . setLinkedRecords (
300- [ otaOperation , ...otaOperations ] ,
301- "otaOperations" ,
302- ) ;
303- }
304- }
239+ const newOperationId = data ?. createManualOtaOperation ?. result ?. id ;
240+ if ( ! newOperationId ) return ;
241+
242+ const newOperation = store . get ( newOperationId ) ;
243+
244+ const storedDevice = store . get ( deviceId ) ;
245+ if ( ! storedDevice || ! newOperation ) return ;
246+
247+ const connection = ConnectionHandler . getConnection (
248+ storedDevice ,
249+ "SoftwareUpdateTab_otaOperations" ,
250+ ) ;
251+ if ( ! connection ) return ;
252+
253+ const edges = connection . getLinkedRecords ( "edges" ) ?? [ ] ;
254+ const alreadyPresent = edges . some (
255+ ( edge ) =>
256+ edge . getLinkedRecord ( "node" ) ?. getDataID ( ) === newOperationId ,
257+ ) ;
258+ if ( alreadyPresent ) return ;
259+ const edge = ConnectionHandler . createEdge (
260+ store ,
261+ connection ,
262+ newOperation ,
263+ "FileDownloadRequestEdge" ,
264+ ) ;
265+ ConnectionHandler . insertEdgeBefore ( connection , edge ) ;
305266 } ,
306267 } ) ;
307268 } ;
308269
270+ const otaOperationsRef = data ?. otaOperations ;
271+
272+ if ( ! otaOperationsRef ) {
273+ return null ;
274+ }
275+
309276 return (
310277 < Tab
311278 eventKey = "device-software-update-tab"
@@ -386,34 +353,13 @@ const DeviceSoftwareUpdateTab = ({
386353 ) }
387354 </ Stack >
388355 </ Suspense >
389- { currentOperation && (
390- < div className = "mt-3" >
391- < FormattedMessage
392- id = "components.DeviceTabs.SoftwareUpdateTab.updatingTo"
393- defaultMessage = "Updating to image <a>{baseImageName}</a>"
394- values = { {
395- a : ( chunks : React . ReactNode ) => (
396- < a
397- target = "_blank"
398- rel = "noreferrer"
399- href = { currentOperation . baseImageUrl }
400- >
401- { chunks }
402- </ a >
403- ) ,
404- baseImageName : currentOperation . baseImageUrl . split ( "/" ) . pop ( ) ,
405- } }
406- />
407- { isRefreshing && < Spinner size = "sm" className = "ms-2" /> }
408- </ div >
409- ) }
410356 < h5 className = "mt-4" >
411357 < FormattedMessage
412358 id = "components.DeviceTabs.SoftwareUpdateTab.updatesHistory"
413359 defaultMessage = "History"
414360 />
415361 </ h5 >
416- < OperationTable deviceRef = { data } />
362+ < OperationTable otaOperationsRef = { otaOperationsRef } />
417363 </ div >
418364 </ Tab >
419365 ) ;
0 commit comments