@@ -13,7 +13,7 @@ import {
1313} from "@forge/react" ;
1414import { Fragment , useCallback , useState } from "react" ;
1515
16- import { usePromise } from "../../common" ;
16+ import { FLAGSMITH_APP , usePromise } from "../../common" ;
1717import {
1818 Environment ,
1919 EnvironmentFeatureState ,
@@ -130,14 +130,12 @@ const makeRows = (projectUrl: string, state: FeatureState) => {
130130} ;
131131
132132type IssueFeatureTableProps = {
133- projectUrl : string ;
134133 environments : Environment [ ] ; // must be non-empty
135134 // list of same feature in the context of each environment
136135 environmentFeatures : Feature [ ] ;
137136} ;
138137
139138const IssueFeatureTable = ( {
140- projectUrl,
141139 environments,
142140 environmentFeatures,
143141} : IssueFeatureTableProps ) : JSX . Element => {
@@ -147,31 +145,68 @@ const IssueFeatureTable = ({
147145 // get id and name from first feature - assume non-empty
148146 const featureId = environmentFeatures [ 0 ] ! . id ;
149147 const featureName = environmentFeatures [ 0 ] ! . name ;
148+ const featureProjectId = environmentFeatures [ 0 ] ! . project ;
149+ const projectUrl = `${ FLAGSMITH_APP } /project/${ featureProjectId } ` ;
150150
151151 /** Read feature state for each environment */
152- const readFeatureState = useCallback (
153- async ( ) : Promise < FeatureState > => ( {
154- featureId,
155- environments : await Promise . all (
156- environments . map ( async ( environment ) => ( {
157- ...( await readEnvironmentFeatureState ( {
152+ const readFeatureState = useCallback ( async ( ) : Promise < FeatureState > => {
153+ const matchingEnvs = environments . filter (
154+ ( env ) => String ( env . project ) === String ( featureProjectId ) ,
155+ ) ;
156+
157+ const environmentFeatureStates = await Promise . all (
158+ matchingEnvs . map ( async ( environment ) => {
159+ try {
160+ const state = await readEnvironmentFeatureState ( {
158161 envApiKey : String ( environment . api_key ) ,
159162 featureName,
160- } ) ) ,
161- name : environment . name ,
162- api_key : String ( environment . api_key ) ,
163- } ) ) ,
164- ) ,
165- counts : environmentFeatures . map ( ( feature ) => ( {
163+ } ) ;
164+
165+ return {
166+ ...state ,
167+ name : environment . name ,
168+ api_key : String ( environment . api_key ) ,
169+ } ;
170+ } catch ( err ) {
171+ console . warn (
172+ `Failed to read feature state for environment "${ environment . name } " (${ environment . id } )` ,
173+ {
174+ featureName,
175+ apiKey : environment . api_key ,
176+ error : err instanceof Error ? err . message : String ( err ) ,
177+ } ,
178+ ) ;
179+ return null ;
180+ }
181+ } ) ,
182+ ) ;
183+
184+ const validStates = environmentFeatureStates . filter (
185+ ( state ) : state is EnvironmentFeatureState => ! ! state ,
186+ ) ;
187+
188+ const relevantFeatures = environmentFeatures . filter (
189+ ( f ) => String ( f . project ) === String ( featureProjectId ) ,
190+ ) ;
191+
192+ return {
193+ featureId,
194+ environments : validStates ,
195+ counts : relevantFeatures . map ( ( feature ) => ( {
166196 variations : feature . multivariate_options . length ,
167197 segments : feature . num_segment_overrides ?? 0 ,
168198 identities : feature . num_identity_overrides ?? 0 ,
169199 } ) ) ,
170- } ) ,
171- [ environments , environmentFeatures ] ,
172- ) ;
200+ } ;
201+ } , [ environments , environmentFeatures ] ) ;
202+
173203 const [ state ] = usePromise (
174- async ( ) => ( error === undefined ? readFeatureState ( ) : undefined ) ,
204+ async ( ) => {
205+ if ( error === undefined ) {
206+ return await readFeatureState ( ) ;
207+ }
208+ return undefined ;
209+ } ,
175210 [ error , readFeatureState ] ,
176211 setError ,
177212 ) ;
@@ -198,47 +233,63 @@ const IssueFeatureTable = ({
198233
199234type FeatureTableData = {
200235 featureId : string ;
201- baseFeature : Feature | undefined ;
202- environmentFeatures : Feature [ ] ;
236+ baseFeature : Feature ;
237+ envFeaturesForThisFeature : Feature [ ] ;
238+ matchingEnvironments : Environment [ ] ;
203239} ;
204240
205241const buildFeatureTableData = ( {
206242 issueFeatureIds,
207243 features,
244+ environments,
208245 environmentsFeatures,
209246} : {
210247 issueFeatureIds : string [ ] ;
211248 features : Feature [ ] ;
249+ environments : Environment [ ] ;
212250 environmentsFeatures : Feature [ ] [ ] ;
213251} ) : FeatureTableData [ ] => {
214- return issueFeatureIds . map ( ( featureId ) => {
215- const baseFeature = features . find ( ( f ) => String ( f . id ) === featureId ) ;
252+ return issueFeatureIds
253+ . map ( ( featureId ) => {
254+ const baseFeature = features . find ( ( f ) => String ( f . id ) === featureId ) ;
255+ if ( ! baseFeature ) {
256+ return null ;
257+ }
216258
217- const envFeaturesForThisFeature = environmentsFeatures
218- . map ( ( envFeatures ) => {
259+ const envFeaturesForThisFeature : Feature [ ] = [ ] ;
260+ const matchingEnvironments : Environment [ ] = [ ] ;
261+
262+ environmentsFeatures . forEach ( ( envFeatures , index ) => {
219263 const matchingFeature = envFeatures . find ( ( f ) => String ( f . id ) === featureId ) ;
220- return matchingFeature ;
221- } )
222- . filter ( Boolean ) as Feature [ ] ;
264+ const environment = environments [ index ] ;
265+ if ( matchingFeature && environment !== undefined ) {
266+ envFeaturesForThisFeature . push ( matchingFeature ) ;
267+ matchingEnvironments . push ( environment ) ;
268+ }
269+ } ) ;
223270
224- return {
225- featureId,
226- baseFeature,
227- environmentFeatures : envFeaturesForThisFeature ,
228- } ;
229- } ) ;
271+ if ( envFeaturesForThisFeature . length === 0 || matchingEnvironments . length === 0 ) {
272+ return null ;
273+ }
274+
275+ return {
276+ featureId,
277+ baseFeature,
278+ envFeaturesForThisFeature,
279+ matchingEnvironments,
280+ } ;
281+ } )
282+ . filter ( ( data ) : data is FeatureTableData => ! ! data ) ;
230283} ;
231284
232285type IssueFeatureTablesProps = {
233- projectUrl : string ;
234286 // environments/environmentsFeatures are assumed to be same length/order
235287 environments : Environment [ ] ;
236288 environmentsFeatures : Feature [ ] [ ] ;
237289 issueFeatureIds : string [ ] ;
238290} ;
239291
240292const IssueFeatureTables = ( {
241- projectUrl,
242293 environments,
243294 environmentsFeatures,
244295 issueFeatureIds,
@@ -254,38 +305,34 @@ const IssueFeatureTables = ({
254305 // iterate features from the first environment to get list of tables
255306 // (id/name/description are the same across environments)
256307 const features = environmentsFeatures [ 0 ] ;
257-
258308 const featureTableData = buildFeatureTableData ( {
259309 issueFeatureIds,
260310 features,
311+ environments,
261312 environmentsFeatures,
262313 } ) ;
263314
264315 return (
265316 < Fragment >
266- { featureTableData . map ( ( { featureId, baseFeature, environmentFeatures } ) => {
267- if ( ! baseFeature ) {
268- return null ;
269- }
270- return (
317+ { featureTableData . map (
318+ ( { featureId, baseFeature, matchingEnvironments, envFeaturesForThisFeature } ) => (
271319 < Fragment key = { featureId } >
272320 < Box xcss = { { marginTop : "space.300" , marginBottom : "space.100" } } >
273321 < Text >
274322 < Strong >
275- { baseFeature . name }
323+ { baseFeature . name } ( { baseFeature . project_name } )
276324 { baseFeature . description ? ": " : "" }
277325 </ Strong >
278326 { baseFeature . description }
279327 </ Text >
280328 </ Box >
281329 < IssueFeatureTable
282- projectUrl = { projectUrl }
283- environments = { environments }
284- environmentFeatures = { environmentFeatures }
330+ environments = { matchingEnvironments }
331+ environmentFeatures = { envFeaturesForThisFeature }
285332 />
286333 </ Fragment >
287- ) ;
288- } ) }
334+ ) ,
335+ ) }
289336 </ Fragment >
290337 ) ;
291338} ;
0 commit comments