feat(graph): show next steps for successful migrations (#30934)
This PR updates Migration UI to display "next steps" when they are provided by a migration. This works by writing `nextSteps` into the Nx Console meta in `migrations.json`. If the `nextSteps` is missing or it's empty, then nothing will be shown. <img width="1555" alt="Screenshot 2025-04-29 at 5 16 49 PM" src="https://github.com/user-attachments/assets/88491632-9b33-421a-887a-b6fbb5676098" /> See: https://www.loom.com/share/c0a4a7dce9df46b5b023fce5e0a3bd2f
This commit is contained in:
parent
9dcab79b10
commit
dcef5c7cf2
@ -12,7 +12,13 @@ import {
|
|||||||
PlayIcon,
|
PlayIcon,
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { Pill } from '@nx/graph-internal/ui-project-details';
|
import { Pill } from '@nx/graph-internal/ui-project-details';
|
||||||
import { useState, forwardRef, useImperativeHandle, useEffect } from 'react';
|
import {
|
||||||
|
useState,
|
||||||
|
forwardRef,
|
||||||
|
useImperativeHandle,
|
||||||
|
useEffect,
|
||||||
|
type ReactNode,
|
||||||
|
} from 'react';
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
|
|
||||||
export interface MigrationCardHandle {
|
export interface MigrationCardHandle {
|
||||||
@ -21,6 +27,30 @@ export interface MigrationCardHandle {
|
|||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertUrlsToLinks(text: string): ReactNode[] {
|
||||||
|
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||||
|
const parts = text.split(urlRegex);
|
||||||
|
const urls = text.match(urlRegex) || [];
|
||||||
|
const result: ReactNode[] = [];
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
if (urls[i - 1]) {
|
||||||
|
result.push(
|
||||||
|
<a
|
||||||
|
key={i}
|
||||||
|
href={urls[i - 1]}
|
||||||
|
target="_blank"
|
||||||
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
{urls[i - 1]}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
} else if (parts[i]) {
|
||||||
|
result.push(parts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export const MigrationCard = forwardRef<
|
export const MigrationCard = forwardRef<
|
||||||
MigrationCardHandle,
|
MigrationCardHandle,
|
||||||
{
|
{
|
||||||
@ -200,6 +230,23 @@ export const MigrationCard = forwardRef<
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{succeeded && migrationResult?.nextSteps?.length ? (
|
||||||
|
<div className="pt-2">
|
||||||
|
<div className="my-2 border-t border-slate-200 dark:border-slate-700/60" />
|
||||||
|
<span className="pb-2 text-sm font-bold">
|
||||||
|
More Information & Next Steps
|
||||||
|
</span>
|
||||||
|
<ul className="list-inside list-disc pl-2">
|
||||||
|
{migrationResult?.nextSteps.map((step, idx) => (
|
||||||
|
<li key={idx} className="text-sm">
|
||||||
|
{convertUrlsToLinks(step)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className="mt-4 flex justify-end gap-2">
|
<div className="mt-4 flex justify-end gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => onViewImplementation()}
|
onClick={() => onViewImplementation()}
|
||||||
@ -266,7 +313,7 @@ export const MigrationCard = forwardRef<
|
|||||||
onFileClick(file);
|
onFileClick(file);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{file.path}
|
<code>{file.path}</code>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -242,7 +242,7 @@ export const PendingApproval: Story = {
|
|||||||
{
|
{
|
||||||
id: 'migration-1',
|
id: 'migration-1',
|
||||||
name: 'migration-1',
|
name: 'migration-1',
|
||||||
description: 'Migrate 1',
|
description: 'This is a test migration',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
package: 'nx',
|
package: 'nx',
|
||||||
implementation: './src/migrations/migration-1.ts',
|
implementation: './src/migrations/migration-1.ts',
|
||||||
@ -250,7 +250,7 @@ export const PendingApproval: Story = {
|
|||||||
{
|
{
|
||||||
id: 'migration-2',
|
id: 'migration-2',
|
||||||
name: 'migration-2',
|
name: 'migration-2',
|
||||||
description: 'Migrate 2',
|
description: 'This is a test migration',
|
||||||
version: '1.0.1',
|
version: '1.0.1',
|
||||||
package: '@nx/react',
|
package: '@nx/react',
|
||||||
implementation: './src/migrations/migration-2.ts',
|
implementation: './src/migrations/migration-2.ts',
|
||||||
@ -271,12 +271,17 @@ export const PendingApproval: Story = {
|
|||||||
type: 'successful',
|
type: 'successful',
|
||||||
changedFiles: [],
|
changedFiles: [],
|
||||||
ref: '123',
|
ref: '123',
|
||||||
|
nextSteps: [],
|
||||||
},
|
},
|
||||||
'migration-2': {
|
'migration-2': {
|
||||||
name: 'migration-2',
|
name: 'migration-2',
|
||||||
type: 'successful',
|
type: 'successful',
|
||||||
changedFiles: [{ path: 'foo.txt', type: 'CREATE' }],
|
changedFiles: [{ path: 'foo.txt', type: 'CREATE' }],
|
||||||
ref: '124',
|
ref: '124',
|
||||||
|
nextSteps: [
|
||||||
|
'Check something: https://nx.dev/docs',
|
||||||
|
'Check another thing: https://nx.dev/docs',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
targetVersion: '20.3.2',
|
targetVersion: '20.3.2',
|
||||||
|
|||||||
@ -27,6 +27,7 @@ export type SuccessfulMigration = {
|
|||||||
name: string;
|
name: string;
|
||||||
changedFiles: Omit<FileChange, 'content'>[];
|
changedFiles: Omit<FileChange, 'content'>[];
|
||||||
ref: string;
|
ref: string;
|
||||||
|
nextSteps?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FailedMigration = {
|
export type FailedMigration = {
|
||||||
@ -135,7 +136,7 @@ export async function runSingleMigration(
|
|||||||
// 2. Bundled into Console, so the version is fixed to what we build Console with.
|
// 2. Bundled into Console, so the version is fixed to what we build Console with.
|
||||||
const updatedMigrateModule = await import('./migrate.js');
|
const updatedMigrateModule = await import('./migrate.js');
|
||||||
|
|
||||||
const { changes: fileChanges } =
|
const { changes: fileChanges, nextSteps } =
|
||||||
await updatedMigrateModule.runNxOrAngularMigration(
|
await updatedMigrateModule.runNxOrAngularMigration(
|
||||||
workspacePath,
|
workspacePath,
|
||||||
migration,
|
migration,
|
||||||
@ -159,7 +160,8 @@ export async function runSingleMigration(
|
|||||||
path: change.path,
|
path: change.path,
|
||||||
type: change.type,
|
type: change.type,
|
||||||
})),
|
})),
|
||||||
gitRefAfter
|
gitRefAfter,
|
||||||
|
nextSteps
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -234,7 +236,8 @@ export function modifyMigrationsJsonMetadata(
|
|||||||
export function addSuccessfulMigration(
|
export function addSuccessfulMigration(
|
||||||
id: string,
|
id: string,
|
||||||
fileChanges: Omit<FileChange, 'content'>[],
|
fileChanges: Omit<FileChange, 'content'>[],
|
||||||
ref: string
|
ref: string,
|
||||||
|
nextSteps: string[]
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
migrationsJsonMetadata: MigrationsJsonMetadata
|
migrationsJsonMetadata: MigrationsJsonMetadata
|
||||||
@ -250,6 +253,7 @@ export function addSuccessfulMigration(
|
|||||||
name: id,
|
name: id,
|
||||||
changedFiles: fileChanges,
|
changedFiles: fileChanges,
|
||||||
ref,
|
ref,
|
||||||
|
nextSteps,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return copied;
|
return copied;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user