Migrations that re-write edit version history

Hi,
We’ve written migrations that convert nested content to block list
We’ve written migrations to convert macros to rte blocks.

Both these migrations work by publishing a new version with the correct schema.
However, this does not affect version history, so roll-back now fails.

Is anyone aware of a tutorial or example code that demonstrates how version history can be altered?

Cheers.
Murray.

Hi there,

Could you share the code you are using to save. I can then compare to my migrations as I’ve not had this reported as an issue, but would be good for to check!

The issue might be that notifications are supressed during a migration. So anything that acts on notifications doesn’t run. For context, I have had an issue with uSync files not getting updated with things I do in a migration. Not sure it this is any help in your specific case, but I solved my issue by running a ‘post migration’:

INotificationAsyncHandler<MigrationPlansExecutedNotification>

And triggering the things that have changed.

Thanks this is useful, I had the same issue when running my custom migrations in a cloud site, not updating the uda. However I was just lazy and made sure to do an export each time - yours is more elegant!

	public async Task<ContentChanges> Convert(IContent content)
	{
		// prepare a delta of changes to apply (list of properties and the new values)
		var summary = _contentConverter.PrepareChanges(content);
		var fresh = _serviceContext.ContentService!.GetById(summary.ContentId)!;
		
		if (fresh.Trashed)
		{
			_logger.LogInformation("ERROR: Content is trashed, skipping conversion: {name} ({key})", fresh.Name, fresh.Key);
			summary.Error = "Content is trashed, skipping conversion.";
			return summary;
		}

		// apply delta of changes.
		foreach (var property in summary.Properties.Where(x => x.Value != null))
		{
			fresh.SetValue(property.Alias, property.Value?.ToString());
		}

		if(fresh.IsDirty() == false)
		{
			summary.Status = "No changes needed";
			return summary;
		}

		try
		{
			_serviceContext.ContentService!.Save(fresh);
			if (fresh.Published) // if there are pending edits publish anyway.
			{
				_serviceContext.ContentService!.Publish(fresh, ["*"]); // publish all cultures
			}
			_logger.LogInformation("Updated Macro->Block on page: {name} ({key})", fresh.Name, fresh.Key);
		}
		catch (Exception e)
		{
			_logger.LogError(e, "FAILED to update Macros->Block on page: {name} ({key}) published:{published}", fresh.Name, fresh.Key, fresh.Published);
			summary.Error = e.Message;
		}
		return summary;
	}

I think I may need to clarify, the rollback mechanism works fine, however the content revision you roll back to is in the old format <?UMBRACO_MACRO … >. The macro content in the newly rolled-back version is not understood by Umbraco 17, so it is not rendered.

I believe that the Umbraco Core team writes migrations that rewrite the entire history, and I’m wondering how to do the same.

e.g., when TinyMCE changed to TipTap, the core migrations rewrite edit history so that you can roll back to a version that used to be TinyMCE, but it has already been converted to the TipTap format.

I believe that the Umbraco Core team writes migrations that rewrite the entire history,

Not that I’m aware of, that would also be wildly inefficient for people who have not done any revision cleanup.

e.g., when TinyMCE changed to TipTap, the core migrations rewrite edit history so that you can roll back to a version that used to be TinyMCE, but it has already been converted to the TipTap format.

No, TipTap can understand the existing Tiny markup just fine, so the property editor is just switched out on the doctype and that’s it.

1 Like

Here’s one of the core migrations

This looks like it’s rewriting every propety value for all history, I don’t see a ‘where’ clause joining on the contentVersion table.

So perhaps this is my example of how to do it.

1 Like

Learned something new today! It seems like an expensive migration, so maybe make sure that you limit the number of versions before you start, if possible.

Yeah I hit this one too, from the other side…

USync Migration handled legacy grid to block grid.. so lots of content in the propertydata table with published version as Blockgrid, but previous versions as legacy grid and the migrator trying to help update all versions failed, so stalled v17 update..

Quickest workaround was to update all versions to the published newest version.. to at least retain some audit history..

;WITH LatestPerNode AS (
    SELECT
        cv.nodeId,
        pd.textValue,
        cv.versionDate,
        ROW_NUMBER() OVER (
            PARTITION BY cv.nodeId
            ORDER BY cv.versionDate DESC
        ) AS rn
    FROM umbracoPropertyData pd
    JOIN umbracoContentVersion cv
        ON pd.versionId = cv.id
    WHERE pd.propertytypeid = 1206
      AND pd.textValue LIKE '{"layout":%'
)
UPDATE oldpd
SET oldpd.textValue = latest.textValue
FROM umbracoPropertyData oldpd
JOIN umbracoContentVersion oldcv
    ON oldpd.versionId = oldcv.id
JOIN LatestPerNode latest
    ON latest.nodeId = oldcv.nodeId
   AND latest.rn = 1
WHERE oldpd.propertytypeid = 1206
  AND oldpd.textValue LIKE '%1 Column%'
  AND oldcv.versionDate < latest.versionDate;