feat(graph): add undo migration option when one is pending approval (#30878)
This PR adds a button for user to undo a migration that's already been applied and pending approval. See: https://www.loom.com/share/97286bdc80ea4538af76a914ef8f0f8b Also, fixes an existing issue where `migrations.json` did not record the correct git sha for each commit. ## Current Behavior When a migration is pending approval, the only option is to accept it. ## Expected Behavior Allow user to undo a migration if they don't want the changes. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
3b0c456bbe
commit
0dc4dbf499
@ -85,6 +85,13 @@ export function MigrateApp({
|
||||
});
|
||||
};
|
||||
|
||||
const onUndoMigration = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'undo-migration',
|
||||
payload: { migration },
|
||||
});
|
||||
};
|
||||
|
||||
const onViewImplementation = (migration: MigrationDetailsWithId) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'view-implementation',
|
||||
@ -109,6 +116,7 @@ export function MigrateApp({
|
||||
onFinish={onFinish}
|
||||
onFileClick={onFileClick}
|
||||
onSkipMigration={onSkipMigration}
|
||||
onUndoMigration={onUndoMigration}
|
||||
onViewImplementation={onViewImplementation}
|
||||
onViewDocumentation={onViewDocumentation}
|
||||
></MigrateUI>
|
||||
|
||||
@ -21,6 +21,7 @@ export function AutomaticMigration(props: {
|
||||
nxConsoleMetadata: MigrationsJsonMetadata;
|
||||
onRunMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onSkipMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onUndoMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onFileClick: (
|
||||
migration: MigrationDetailsWithId,
|
||||
file: Omit<FileChange, 'content'>
|
||||
@ -87,6 +88,7 @@ export function AutomaticMigration(props: {
|
||||
isInit={isInit}
|
||||
onRunMigration={props.onRunMigration}
|
||||
onSkipMigration={props.onSkipMigration}
|
||||
onUndoMigration={props.onUndoMigration}
|
||||
onFileClick={props.onFileClick}
|
||||
onViewImplementation={props.onViewImplementation}
|
||||
onViewDocumentation={props.onViewDocumentation}
|
||||
|
||||
@ -33,6 +33,7 @@ export interface MigrationTimelineProps {
|
||||
isInit: boolean;
|
||||
onRunMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onSkipMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onUndoMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onFileClick: (
|
||||
migration: MigrationDetailsWithId,
|
||||
file: Omit<FileChange, 'content'>
|
||||
@ -53,6 +54,7 @@ export function MigrationTimeline({
|
||||
currentMigrationHasChanges,
|
||||
onRunMigration,
|
||||
onSkipMigration,
|
||||
onUndoMigration,
|
||||
onFileClick,
|
||||
onViewImplementation,
|
||||
onViewDocumentation,
|
||||
@ -335,6 +337,18 @@ export function MigrationTimeline({
|
||||
)}
|
||||
|
||||
{currentMigrationHasChanges && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleMigrationExpanded(currentMigration.id);
|
||||
onUndoMigration(currentMigration);
|
||||
}}
|
||||
type="button"
|
||||
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"
|
||||
>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />{' '}
|
||||
Undo and Skip
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleMigrationExpanded(currentMigration.id);
|
||||
@ -346,6 +360,7 @@ export function MigrationTimeline({
|
||||
<CheckIcon className="h-5 w-5" aria-hidden="true" />{' '}
|
||||
Approve Changes
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -290,6 +290,9 @@ export const PendingApproval: Story = {
|
||||
onSkipMigration: (migration) => {
|
||||
console.log('skip migration', migration);
|
||||
},
|
||||
onUndoMigration: (migration) => {
|
||||
console.log('undo migration', migration);
|
||||
},
|
||||
onFileClick: (migration, file) => {
|
||||
console.log('file click', migration, file);
|
||||
},
|
||||
|
||||
@ -36,6 +36,7 @@ export interface MigrateUIProps {
|
||||
}
|
||||
) => void;
|
||||
onSkipMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onUndoMigration: (migration: MigrationDetailsWithId) => void;
|
||||
onCancel: () => void;
|
||||
onFinish: (squashCommits: boolean) => void;
|
||||
onFileClick: (
|
||||
@ -188,13 +189,10 @@ export function MigrateUI(props: MigrateUIProps) {
|
||||
onRunMigration={(migration) =>
|
||||
props.onRunMigration(migration, { createCommits })
|
||||
}
|
||||
onSkipMigration={(migration) => props.onSkipMigration(migration)}
|
||||
onViewImplementation={(migration) =>
|
||||
props.onViewImplementation(migration)
|
||||
}
|
||||
onViewDocumentation={(migration) =>
|
||||
props.onViewDocumentation(migration)
|
||||
}
|
||||
onSkipMigration={props.onSkipMigration}
|
||||
onUndoMigration={props.onUndoMigration}
|
||||
onViewImplementation={props.onViewImplementation}
|
||||
onViewDocumentation={props.onViewDocumentation}
|
||||
onFileClick={props.onFileClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -171,6 +171,16 @@ export async function runSingleMigration(
|
||||
cwd: workspacePath,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
// The revision changes after the amend, so we need to update it
|
||||
const amendedGitRef = execSync('git rev-parse HEAD', {
|
||||
cwd: workspacePath,
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
|
||||
modifyMigrationsJsonMetadata(
|
||||
workspacePath,
|
||||
updateRefForSuccessfulMigration(migration.id, amendedGitRef)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
modifyMigrationsJsonMetadata(
|
||||
@ -245,6 +255,24 @@ export function addSuccessfulMigration(
|
||||
};
|
||||
}
|
||||
|
||||
export function updateRefForSuccessfulMigration(id: string, ref: string) {
|
||||
return (
|
||||
migrationsJsonMetadata: MigrationsJsonMetadata
|
||||
): MigrationsJsonMetadata => {
|
||||
const copied = { ...migrationsJsonMetadata };
|
||||
if (!copied.completedMigrations) {
|
||||
copied.completedMigrations = {};
|
||||
}
|
||||
const existing = copied.completedMigrations[id];
|
||||
if (existing && existing.type === 'successful') {
|
||||
existing.ref = ref;
|
||||
} else {
|
||||
throw new Error(`Attempted to update ref for unsuccessful migration`);
|
||||
}
|
||||
return copied;
|
||||
};
|
||||
}
|
||||
|
||||
export function addFailedMigration(id: string, error: string) {
|
||||
return (migrationsJsonMetadata: MigrationsJsonMetadata) => {
|
||||
const copied = { ...migrationsJsonMetadata };
|
||||
@ -307,3 +335,19 @@ export function readMigrationsJsonMetadata(
|
||||
const migrationsJson = JSON.parse(readFileSync(migrationsJsonPath, 'utf-8'));
|
||||
return migrationsJson['nx-console'];
|
||||
}
|
||||
|
||||
export function undoMigration(workspacePath: string, id: string) {
|
||||
return (migrationsJsonMetadata: MigrationsJsonMetadata) => {
|
||||
const existing = migrationsJsonMetadata.completedMigrations[id];
|
||||
if (existing.type !== 'successful')
|
||||
throw new Error(`undoMigration called on unsuccessful migration: ${id}`);
|
||||
execSync(`git reset --hard ${existing.ref}^`, {
|
||||
cwd: workspacePath,
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
migrationsJsonMetadata.completedMigrations[id] = {
|
||||
type: 'skipped',
|
||||
};
|
||||
return migrationsJsonMetadata;
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user