1- /*
2- * This file is part of Edgehog.
3- *
4- * Copyright 2023-2025 SECO Mind Srl
5- *
6- * Licensed under the Apache License, Version 2.0 (the "License");
7- * you may not use this file except in compliance with the License.
8- * You may obtain a copy of the License at
9- *
10- * http://www.apache.org/licenses/LICENSE-2.0
11- *
12- * Unless required by applicable law or agreed to in writing, software
13- * distributed under the License is distributed on an "AS IS" BASIS,
14- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15- * See the License for the specific language governing permissions and
16- * limitations under the License.
17- *
18- * SPDX-License-Identifier: Apache-2.0
19- */
20-
21- import { Suspense , useCallback , useEffect , useState } from "react" ;
1+ // This file is part of Edgehog.
2+ //
3+ // Copyright 2023 - 2026 SECO Mind Srl
4+ //
5+ // Licensed under the Apache License, Version 2.0 (the "License");
6+ // you may not use this file except in compliance with the License.
7+ // You may obtain a copy of the License at
8+ //
9+ // http://www.apache.org/licenses/LICENSE-2.0
10+ //
11+ // Unless required by applicable law or agreed to in writing, software
12+ // distributed under the License is distributed on an "AS IS" BASIS,
13+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ // See the License for the specific language governing permissions and
15+ // limitations under the License.
16+ //
17+ // SPDX-License-Identifier: Apache-2.0
18+
19+ import { Suspense , useCallback , useEffect , useMemo , useState } from "react" ;
2220import { useParams } from "react-router-dom" ;
2321import { FormattedMessage } from "react-intl" ;
2422import { ErrorBoundary } from "react-error-boundary" ;
@@ -36,6 +34,7 @@ import type {
3634} from "@/api/__generated__/BaseImage_getBaseImage_Query.graphql" ;
3735import type { BaseImage_updateBaseImage_Mutation } from "@/api/__generated__/BaseImage_updateBaseImage_Mutation.graphql" ;
3836import type { BaseImage_deleteBaseImage_Mutation } from "@/api/__generated__/BaseImage_deleteBaseImage_Mutation.graphql" ;
37+ import type { BaseImage_getRelatedUpdateCampaigns_Query } from "@/api/__generated__/BaseImage_getRelatedUpdateCampaigns_Query.graphql" ;
3938import Alert from "@/components/Alert" ;
4039import Center from "@/components/Center" ;
4140import DeleteModal from "@/components/DeleteModal" ;
@@ -84,16 +83,45 @@ const DELETE_BASE_IMAGE_MUTATION = graphql`
8483 result {
8584 id
8685 }
86+ errors {
87+ message
88+ }
89+ }
90+ }
91+ ` ;
92+
93+ const RELATED_UPDATE_CAMPAIGNS_QUERY = graphql `
94+ query BaseImage_getRelatedUpdateCampaigns_Query {
95+ updateCampaigns {
96+ edges {
97+ node {
98+ name
99+ status
100+ campaignMechanism {
101+ __typename
102+ ... on FirmwareUpgrade {
103+ baseImage {
104+ id
105+ }
106+ }
107+ }
108+ }
109+ }
87110 }
88111 }
89112` ;
90113
91114type BaseImageContentProps = {
92115 baseImage : NonNullable < BaseImage_getBaseImage_Query$data [ "baseImage" ] > ;
93116 queryRef : BaseImage_getBaseImage_Query$data ;
117+ getRelatedUpdateCampaignsQuery : PreloadedQuery < BaseImage_getRelatedUpdateCampaigns_Query > ;
94118} ;
95119
96- const BaseImageContent = ( { baseImage, queryRef } : BaseImageContentProps ) => {
120+ const BaseImageContent = ( {
121+ baseImage,
122+ queryRef,
123+ getRelatedUpdateCampaignsQuery,
124+ } : BaseImageContentProps ) => {
97125 const baseImageId = baseImage . id ;
98126 const baseImageCollectionId = baseImage . baseImageCollection . id ;
99127 const navigate = useNavigate ( ) ;
@@ -108,10 +136,28 @@ const BaseImageContent = ({ baseImage, queryRef }: BaseImageContentProps) => {
108136 const [ deleteBaseImage , isDeletingBaseImage ] =
109137 useMutation < BaseImage_deleteBaseImage_Mutation > ( DELETE_BASE_IMAGE_MUTATION ) ;
110138
139+ // TODO: filter per base image in backend
140+ const relatedUpdateCampaignsData = usePreloadedQuery (
141+ RELATED_UPDATE_CAMPAIGNS_QUERY ,
142+ getRelatedUpdateCampaignsQuery ,
143+ ) ;
144+ const runningUpdateCampaigns = useMemo (
145+ ( ) =>
146+ relatedUpdateCampaignsData ?. updateCampaigns ?. edges
147+ ?. map ( ( { node } ) => node )
148+ . filter (
149+ ( campaign ) =>
150+ campaign . status !== "FINISHED" &&
151+ campaign . campaignMechanism . __typename === "FirmwareUpgrade" &&
152+ campaign . campaignMechanism . baseImage ?. id === baseImageId ,
153+ ) ,
154+ [ relatedUpdateCampaignsData , baseImageId ] ,
155+ ) ;
156+
111157 const handleDeleteBaseImage = useCallback ( ( ) => {
112158 deleteBaseImage ( {
113159 variables : { baseImageId } ,
114- onCompleted ( data , errors ) {
160+ onCompleted ( _data , errors ) {
115161 if ( ! errors || errors . length === 0 || errors [ 0 ] . code === "not_found" ) {
116162 return navigate ( {
117163 route : Route . baseImageCollectionsEdit ,
@@ -121,7 +167,15 @@ const BaseImageContent = ({ baseImage, queryRef }: BaseImageContentProps) => {
121167
122168 const errorFeedback = errors
123169 . map ( ( { fields, message } ) =>
124- fields . length ? `${ fields . join ( " " ) } ${ message } ` : message ,
170+ message . includes ( "in use by at least one running campaign" ) &&
171+ runningUpdateCampaigns
172+ ? `${ message } :\n` +
173+ runningUpdateCampaigns
174+ . map ( ( campaign ) => campaign . name )
175+ . join ( "\n" )
176+ : fields . length
177+ ? `${ fields . join ( " " ) } ${ message } `
178+ : message ,
125179 )
126180 . join ( ". \n" ) ;
127181 setErrorFeedback ( errorFeedback ) ;
@@ -149,7 +203,13 @@ const BaseImageContent = ({ baseImage, queryRef }: BaseImageContentProps) => {
149203 ?. invalidateRecord ( ) ;
150204 } ,
151205 } ) ;
152- } , [ deleteBaseImage , baseImageId , baseImageCollectionId , navigate ] ) ;
206+ } , [
207+ deleteBaseImage ,
208+ baseImageId ,
209+ baseImageCollectionId ,
210+ navigate ,
211+ runningUpdateCampaigns ,
212+ ] ) ;
153213
154214 const [ updateBaseImage , isUpdatingBaseImage ] =
155215 useMutation < BaseImage_updateBaseImage_Mutation > ( UPDATE_BASE_IMAGE_MUTATION ) ;
@@ -158,7 +218,7 @@ const BaseImageContent = ({ baseImage, queryRef }: BaseImageContentProps) => {
158218 ( baseImageChanges : BaseImageChanges ) => {
159219 updateBaseImage ( {
160220 variables : { baseImageId, input : baseImageChanges } ,
161- onCompleted ( data , errors ) {
221+ onCompleted ( _data , errors ) {
162222 if ( errors ) {
163223 const errorFeedback = errors
164224 . map ( ( { fields, message } ) =>
@@ -242,9 +302,13 @@ const BaseImageContent = ({ baseImage, queryRef }: BaseImageContentProps) => {
242302
243303type BaseImageWrapperProps = {
244304 getBaseImageQuery : PreloadedQuery < BaseImage_getBaseImage_Query > ;
305+ getRelatedUpdateCampaignsQuery : PreloadedQuery < BaseImage_getRelatedUpdateCampaigns_Query > ;
245306} ;
246307
247- const BaseImageWrapper = ( { getBaseImageQuery } : BaseImageWrapperProps ) => {
308+ const BaseImageWrapper = ( {
309+ getBaseImageQuery,
310+ getRelatedUpdateCampaignsQuery,
311+ } : BaseImageWrapperProps ) => {
248312 const { baseImageCollectionId = "" } = useParams ( ) ;
249313
250314 const queryData = usePreloadedQuery ( GET_BASE_IMAGE_QUERY , getBaseImageQuery ) ;
@@ -273,7 +337,11 @@ const BaseImageWrapper = ({ getBaseImageQuery }: BaseImageWrapperProps) => {
273337 }
274338
275339 return (
276- < BaseImageContent baseImage = { queryData . baseImage } queryRef = { queryData } />
340+ < BaseImageContent
341+ baseImage = { queryData . baseImage }
342+ queryRef = { queryData }
343+ getRelatedUpdateCampaignsQuery = { getRelatedUpdateCampaignsQuery }
344+ />
277345 ) ;
278346} ;
279347
@@ -289,6 +357,17 @@ const BaseImagePage = () => {
289357
290358 useEffect ( fetchBaseImage , [ fetchBaseImage ] ) ;
291359
360+ const [ getRelatedUpdateCampaignsQuery , getRelatedUpdateCampaigns ] =
361+ useQueryLoader < BaseImage_getRelatedUpdateCampaigns_Query > (
362+ RELATED_UPDATE_CAMPAIGNS_QUERY ,
363+ ) ;
364+
365+ const fetchRelatedUpdateCampaigns = useCallback ( ( ) => {
366+ getRelatedUpdateCampaigns ( { } , { fetchPolicy : "network-only" } ) ;
367+ } , [ getRelatedUpdateCampaigns ] ) ;
368+
369+ useEffect ( fetchRelatedUpdateCampaigns , [ fetchRelatedUpdateCampaigns ] ) ;
370+
292371 return (
293372 < Suspense
294373 fallback = {
@@ -305,8 +384,11 @@ const BaseImagePage = () => {
305384 ) }
306385 onReset = { fetchBaseImage }
307386 >
308- { getBaseImageQuery && (
309- < BaseImageWrapper getBaseImageQuery = { getBaseImageQuery } />
387+ { getBaseImageQuery && getRelatedUpdateCampaignsQuery && (
388+ < BaseImageWrapper
389+ getBaseImageQuery = { getBaseImageQuery }
390+ getRelatedUpdateCampaignsQuery = { getRelatedUpdateCampaignsQuery }
391+ />
310392 ) }
311393 </ ErrorBoundary >
312394 </ Suspense >
0 commit comments