@@ -20,6 +20,17 @@ import {
20
20
type ReactNode ,
21
21
} from 'react' ;
22
22
import { AnimatePresence , motion } from 'framer-motion' ;
23
+ import type { Interpreter } from 'xstate' ;
24
+ import type {
25
+ AutomaticMigrationState ,
26
+ AutomaticMigrationEvents ,
27
+ } from '../state/automatic/types' ;
28
+ import { useSelector } from '@xstate/react' ;
29
+ import {
30
+ currentMigrationHasChanges ,
31
+ getMigrationType ,
32
+ isMigrationRunning ,
33
+ } from '../state/automatic/selectors' ;
23
34
24
35
export interface MigrationCardHandle {
25
36
expand : ( ) => void ;
@@ -37,6 +48,7 @@ function convertUrlsToLinks(text: string): ReactNode[] {
37
48
result . push (
38
49
< a
39
50
key = { i }
51
+ rel = "noopener noreferrer"
40
52
href = { urls [ i - 1 ] }
41
53
target = "_blank"
42
54
className = "text-blue-500 hover:underline"
@@ -54,6 +66,13 @@ function convertUrlsToLinks(text: string): ReactNode[] {
54
66
export const MigrationCard = forwardRef <
55
67
MigrationCardHandle ,
56
68
{
69
+ actor : Interpreter <
70
+ AutomaticMigrationState ,
71
+ any ,
72
+ AutomaticMigrationEvents ,
73
+ any ,
74
+ any
75
+ > ;
57
76
migration : MigrationDetailsWithId ;
58
77
nxConsoleMetadata : MigrationsJsonMetadata ;
59
78
isSelected ?: boolean ;
@@ -62,11 +81,11 @@ export const MigrationCard = forwardRef<
62
81
onFileClick : ( file : Omit < FileChange , 'content' > ) => void ;
63
82
onViewImplementation : ( ) => void ;
64
83
onViewDocumentation : ( ) => void ;
65
- forceIsRunning ?: boolean ;
66
84
isExpanded ?: boolean ;
67
85
}
68
86
> ( function MigrationCard (
69
87
{
88
+ actor,
70
89
migration,
71
90
nxConsoleMetadata,
72
91
isSelected,
@@ -75,7 +94,6 @@ export const MigrationCard = forwardRef<
75
94
onFileClick,
76
95
onViewImplementation,
77
96
onViewDocumentation,
78
- forceIsRunning,
79
97
isExpanded : isExpandedProp ,
80
98
} ,
81
99
ref
@@ -99,14 +117,35 @@ export const MigrationCard = forwardRef<
99
117
} , [ isExpandedProp ] ) ;
100
118
101
119
const migrationResult = nxConsoleMetadata . completedMigrations ?. [ migration . id ] ;
102
- const succeeded = migrationResult ?. type === 'successful' ;
103
- const failed = migrationResult ?. type === 'failed' ;
104
- const skipped = migrationResult ?. type === 'skipped' ;
105
- const inProgress = nxConsoleMetadata . runningMigrations ?. includes (
106
- migration . id
107
- ) ;
108
120
109
- const madeChanges = succeeded && ! ! migrationResult ?. changedFiles . length ;
121
+ const filesChanged =
122
+ migrationResult ?. type === 'successful' ? migrationResult . changedFiles : [ ] ;
123
+
124
+ const nextSteps =
125
+ migrationResult ?. type === 'successful' ? migrationResult . nextSteps : [ ] ;
126
+
127
+ const isSucceeded = useSelector (
128
+ actor ,
129
+ ( state ) => getMigrationType ( state . context , migration . id ) === 'successful'
130
+ ) ;
131
+ const isFailed = useSelector (
132
+ actor ,
133
+ ( state ) => getMigrationType ( state . context , migration . id ) === 'failed'
134
+ ) ;
135
+ const isSkipped = useSelector (
136
+ actor ,
137
+ ( state ) => getMigrationType ( state . context , migration . id ) === 'skipped'
138
+ ) ;
139
+ const isStopped = useSelector (
140
+ actor ,
141
+ ( state ) => getMigrationType ( state . context , migration . id ) === 'stopped'
142
+ ) ;
143
+ const hasChanges = useSelector ( actor , ( state ) =>
144
+ currentMigrationHasChanges ( state . context )
145
+ ) ;
146
+ const isRunning = useSelector ( actor , ( state ) =>
147
+ isMigrationRunning ( state . context , migration . id )
148
+ ) ;
110
149
111
150
const renderSelectBox = onSelect && isSelected !== undefined ;
112
151
@@ -130,9 +169,9 @@ export const MigrationCard = forwardRef<
130
169
value = { migration . id }
131
170
type = "checkbox"
132
171
className = { `h-4 w-4 ${
133
- succeeded
172
+ isSucceeded
134
173
? 'accent-green-600 dark:accent-green-500'
135
- : failed
174
+ : isFailed
136
175
? 'accent-red-600 dark:accent-red-500'
137
176
: 'accent-blue-500 dark:accent-sky-500'
138
177
} `}
@@ -169,10 +208,10 @@ export const MigrationCard = forwardRef<
169
208
170
209
< div className = "flex items-center gap-2" >
171
210
{ ' ' }
172
- { succeeded && ! madeChanges && (
211
+ { isSucceeded && ! hasChanges && (
173
212
< Pill text = "No changes made" color = "green" />
174
213
) }
175
- { succeeded && madeChanges && (
214
+ { isSucceeded && hasChanges && (
176
215
< div >
177
216
< div
178
217
className = "cursor-pointer"
@@ -182,38 +221,43 @@ export const MigrationCard = forwardRef<
182
221
>
183
222
< Pill
184
223
key = "changes"
185
- text = { `${ migrationResult ?. changedFiles . length } changes` }
224
+ text = { `${ filesChanged . length } changes` }
186
225
color = "green"
187
226
/>
188
227
</ div >
189
228
</ div >
190
229
) }
191
- { failed && (
230
+ { isFailed && (
192
231
< div >
193
232
< Pill text = "Failed" color = "red" />
194
233
</ div >
195
234
) }
196
- { skipped && (
235
+ { isSkipped && (
197
236
< div >
198
237
< Pill text = "Skipped" color = "grey" />
199
238
</ div >
200
239
) }
201
- { ( onRunMigration || forceIsRunning ) && (
240
+ { isStopped && (
241
+ < div >
242
+ < Pill text = "Stopped" color = "yellow" />
243
+ </ div >
244
+ ) }
245
+ { onRunMigration && ! isStopped && (
202
246
< span
203
247
className = { `rounded-md p-1 text-sm ring-1 ring-inset transition-colors ${
204
- succeeded
248
+ isSucceeded
205
249
? 'bg-green-50 text-green-700 ring-green-200 hover:bg-green-100 dark:bg-green-900/20 dark:text-green-500 dark:ring-green-900/30 dark:hover:bg-green-900/30'
206
- : failed
250
+ : isFailed
207
251
? 'bg-red-50 text-red-700 ring-red-200 hover:bg-red-100 dark:bg-red-900/20 dark:text-red-500 dark:ring-red-900/30 dark:hover:bg-red-900/30'
208
252
: 'bg-inherit text-slate-600 ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60'
209
253
} `}
210
254
>
211
- { inProgress || forceIsRunning ? (
255
+ { isRunning ? (
212
256
< ArrowPathIcon
213
257
className = "h-6 w-6 animate-spin cursor-not-allowed text-blue-500"
214
258
aria-label = "Migration in progress"
215
259
/>
216
- ) : ! succeeded && ! failed ? (
260
+ ) : ! isSucceeded && ! isFailed && ! isStopped ? (
217
261
< PlayIcon
218
262
onClick = { onRunMigration }
219
263
className = "h-6 w-6 !cursor-pointer"
@@ -230,14 +274,14 @@ export const MigrationCard = forwardRef<
230
274
) }
231
275
</ div >
232
276
</ div >
233
- { succeeded && migrationResult ?. nextSteps ?. length ? (
277
+ { isSucceeded && nextSteps ?. length ? (
234
278
< div className = "pt-2" >
235
279
< div className = "my-2 border-t border-slate-200 dark:border-slate-700/60" />
236
280
< span className = "pb-2 text-sm font-bold" >
237
281
More Information & Next Steps
238
282
</ span >
239
283
< ul className = "list-inside list-disc pl-2" >
240
- { migrationResult ?. nextSteps . map ( ( step , idx ) => (
284
+ { nextSteps . map ( ( step , idx ) => (
241
285
< li key = { idx } className = "text-sm" >
242
286
{ convertUrlsToLinks ( step ) }
243
287
</ li >
@@ -255,7 +299,7 @@ export const MigrationCard = forwardRef<
255
299
< CodeBracketIcon className = "h-4 w-4" />
256
300
View Source
257
301
</ button >
258
- { failed && (
302
+ { isFailed && (
259
303
< button
260
304
className = "flex items-center gap-2 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition-colors hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700"
261
305
onClick = { ( ) => {
@@ -266,7 +310,7 @@ export const MigrationCard = forwardRef<
266
310
{ isExpanded ? 'Hide Errors' : 'View Errors' }
267
311
</ button >
268
312
) }
269
- { succeeded && madeChanges && (
313
+ { isSucceeded && hasChanges && (
270
314
< button
271
315
className = "flex items-center gap-2 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm transition-colors hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700"
272
316
onClick = { ( ) => {
@@ -279,21 +323,35 @@ export const MigrationCard = forwardRef<
279
323
) }
280
324
</ div >
281
325
< AnimatePresence >
282
- { failed && isExpanded && (
326
+ { isFailed && isExpanded && (
327
+ < motion . div
328
+ initial = { { opacity : 0 , height : 0 } }
329
+ animate = { { opacity : 1 , height : isExpanded ? 'auto' : 0 } }
330
+ exit = { { opacity : 0 , height : 0 } }
331
+ transition = { { duration : 0.3 , ease : 'easeInOut' } }
332
+ className = "flex overflow-hidden pt-2"
333
+ >
334
+ < pre > { ( migrationResult as any ) ?. error } </ pre >
335
+ </ motion . div >
336
+ ) }
337
+ </ AnimatePresence >
338
+
339
+ < AnimatePresence >
340
+ { isStopped && isExpanded && (
283
341
< motion . div
284
342
initial = { { opacity : 0 , height : 0 } }
285
343
animate = { { opacity : 1 , height : isExpanded ? 'auto' : 0 } }
286
344
exit = { { opacity : 0 , height : 0 } }
287
345
transition = { { duration : 0.3 , ease : 'easeInOut' } }
288
346
className = "flex overflow-hidden pt-2"
289
347
>
290
- < pre > { migrationResult ?. error } </ pre >
348
+ < pre > { ( migrationResult as any ) ?. error } </ pre >
291
349
</ motion . div >
292
350
) }
293
351
</ AnimatePresence >
294
352
295
353
< AnimatePresence >
296
- { succeeded && madeChanges && isExpanded && (
354
+ { isSucceeded && hasChanges && isExpanded && (
297
355
< motion . div
298
356
initial = { { opacity : 0 , height : 0 } }
299
357
animate = { { opacity : 1 , height : isExpanded ? 'auto' : 0 } }
@@ -304,7 +362,7 @@ export const MigrationCard = forwardRef<
304
362
< div className = "my-2 border-t border-slate-200 dark:border-slate-700/60" > </ div >
305
363
< span className = "pb-2 text-sm font-bold" > File Changes</ span >
306
364
< ul className = "flex flex-col gap-2" >
307
- { migrationResult ?. changedFiles . map ( ( file ) => {
365
+ { filesChanged . map ( ( file ) => {
308
366
return (
309
367
< li
310
368
className = "cursor-pointer text-sm hover:underline"
0 commit comments