From 01047a9a9cac326b4ea3f003728488fd670129b0 Mon Sep 17 00:00:00 2001 From: Pedro Bonamin <46196328+pedrobonamin@users.noreply.github.com> Date: Tue, 18 Feb 2025 11:56:32 +0100 Subject: [PATCH] fix(core): infer archive, unarchive and publish from release translog (#8679) --- .../sanity/src/core/releases/store/types.ts | 1 - .../__fixtures__/mock-translog-events.ts | 392 ++++++++++++++++++ .../events/buildReleaseEditEvents.test.ts | 43 ++ .../detail/events/buildReleaseEditEvents.ts | 44 +- .../detail/events/getReleaseEditEvents.ts | 4 +- .../tool/detail/events/getReleaseEvents.ts | 21 +- 6 files changed, 495 insertions(+), 10 deletions(-) create mode 100644 packages/sanity/src/core/releases/tool/detail/events/__fixtures__/mock-translog-events.ts diff --git a/packages/sanity/src/core/releases/store/types.ts b/packages/sanity/src/core/releases/store/types.ts index 24eac5cc53f..c789b14d3c5 100644 --- a/packages/sanity/src/core/releases/store/types.ts +++ b/packages/sanity/src/core/releases/store/types.ts @@ -31,7 +31,6 @@ export type ReleaseState = export type ReleaseFinalDocumentState = { /** Document ID */ id: string - revisionId: string } /** diff --git a/packages/sanity/src/core/releases/tool/detail/events/__fixtures__/mock-translog-events.ts b/packages/sanity/src/core/releases/tool/detail/events/__fixtures__/mock-translog-events.ts new file mode 100644 index 00000000000..61489f1f0e8 --- /dev/null +++ b/packages/sanity/src/core/releases/tool/detail/events/__fixtures__/mock-translog-events.ts @@ -0,0 +1,392 @@ +import {type TransactionLogEventWithEffects} from '@sanity/types' + +import {type ReleaseDocument} from '../../../../store/types' + +export const RELEASE_STATE_CHANGE: { + document: ReleaseDocument + transactions: TransactionLogEventWithEffects[] +} = { + document: { + _type: 'system.release', + _id: '_.releases.rY3Ko1I4q', + state: 'published', + metadata: { + releaseType: 'asap', + description: '', + title: 'To be published', + }, + _createdAt: '2025-02-17T15:37:30Z', + name: 'rY3Ko1I4q', + _updatedAt: '2025-02-17T15:50:22Z', + userId: 'p8xDvUMxC', + publishAt: '2025-02-17T15:50:19.692089333Z', + finalDocumentStates: [ + { + id: 'versions.rY3Ko1I4q.f8dece19-c458-4cff-bf76-732b00617183', + }, + ], + publishedAt: '2025-02-17T15:50:22.318027688Z', + _rev: 'ad56vrBQ94RnZtJ3uXY2W4', + }, + transactions: [ + { + id: 'ad56vrBQ94RnZtJ3uXY2W4', + timestamp: '2025-02-17T15:50:22.301295Z', + author: 'p8xDvUMxC', + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [ + 19, + 9, + 11, + 3, + 23, + 0, + 18, + 22, + '2', + 23, + 19, + 20, + 15, + 17, + [ + { + id: 'versions.rY3Ko1I4q.f8dece19-c458-4cff-bf76-732b00617183', + }, + ], + 'finalDocumentStates', + 17, + '2025-02-17T15:50:22.318027688Z', + 'publishedAt', + 11, + 7, + 23, + 0, + 7, + 22, + 'ed', + 15, + ], + revert: [ + 19, + 4, + 19, + 8, + 11, + 3, + 23, + 0, + 18, + 22, + '1', + 23, + 19, + 20, + 15, + 11, + 9, + 23, + 0, + 7, + 22, + 'ing', + 15, + 17, + 'release-rY3Ko1I4q-HjgIO3PPq5URB0RPr1deq1', + 'workflowId', + ], + }, + }, + }, + { + id: 'ad56vrBQ94RnZtJ3uXY2T4', + timestamp: '2025-02-17T15:50:21.242977Z', + author: 'p8xDvUMxC', + + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [11, 3, 23, 0, 17, 22, '21', 23, 19, 20, 15, 17, 'publishing', 'state'], + revert: [11, 3, 23, 0, 17, 22, '19', 23, 19, 20, 15, 17, 'scheduled', 'state'], + }, + }, + }, + { + id: 'ad56vrBQ94RnZtJ3uXY2Q4', + timestamp: '2025-02-17T15:50:19.843869Z', + author: 'p8xDvUMxC', + + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [11, 7, 23, 0, 7, 22, 'ed', 15], + revert: [11, 7, 23, 0, 7, 22, 'ing', 15], + }, + }, + }, + { + id: 'HjgIO3PPq5URB0RPr1demX', + timestamp: '2025-02-17T15:50:19.684661Z', + author: 'p8xDvUMxC', + + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [ + 11, + 3, + 23, + 0, + 14, + 22, + '50:19', + 23, + 19, + 20, + 15, + 17, + '2025-02-17T15:50:19.692089333Z', + 'publishAt', + 17, + 'scheduling', + 'state', + 17, + 'release-rY3Ko1I4q-HjgIO3PPq5URB0RPr1deq1', + 'workflowId', + ], + revert: [ + 19, + 6, + 19, + 9, + 11, + 3, + 23, + 0, + 14, + 22, + '49:47', + 23, + 19, + 20, + 15, + 17, + 'active', + 'state', + ], + }, + }, + }, + { + id: 'HjgIO3PPq5URB0RPr1cN6P', + timestamp: '2025-02-17T15:49:47.189728Z', + author: 'p8xDvUMxC', + mutations: [ + { + patch: { + id: '_.releases.rY3Ko1I4q', + unsetIsEmpty: false, + }, + }, + ], + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [ + 11, + 3, + 23, + 0, + 15, + 22, + '9:4', + 23, + 18, + 20, + 15, + 10, + 4, + 11, + 2, + 23, + 0, + 6, + 22, + 'publish', + 23, + 12, + 14, + 15, + 15, + ], + revert: [ + 11, + 3, + 23, + 0, + 15, + 22, + '8:3', + 23, + 18, + 20, + 15, + 10, + 4, + 11, + 2, + 23, + 0, + 6, + 22, + 'archiv', + 23, + 13, + 15, + 15, + 15, + ], + }, + }, + }, + { + id: 'ad56vrBQ94RnZtJ3uXY2N4', + timestamp: '2025-02-17T15:48:37.430863Z', + author: 'p8xDvUMxC', + + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [19, 8, 17, 'active', 'state'], + revert: [ + 17, + 'unarchiving', + 'state', + 17, + 'release-rY3Ko1I4q-HjgIO3PPq5URB0RPr1aAHB', + 'workflowId', + ], + }, + }, + }, + { + id: 'HjgIO3PPq5URB0RPr1aADh', + timestamp: '2025-02-17T15:48:37.238598Z', + author: 'p8xDvUMxC', + + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [ + 11, + 3, + 23, + 0, + 14, + 22, + '48:37', + 23, + 19, + 20, + 15, + 17, + 'unarchiving', + 'state', + 17, + 'release-rY3Ko1I4q-HjgIO3PPq5URB0RPr1aAHB', + 'workflowId', + ], + revert: [19, 8, 11, 3, 23, 0, 14, 22, '37:41', 23, 19, 20, 15, 17, 'archived', 'state'], + }, + }, + }, + { + id: 'ad56vrBQ94RnZtJ3uXY2K4', + timestamp: '2025-02-17T15:37:41.591300Z', + author: 'p8xDvUMxC', + + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [19, 8, 11, 6, 23, 0, 6, 22, 'ed', 15], + revert: [ + 11, + 6, + 23, + 0, + 6, + 22, + 'ing', + 15, + 17, + 'release-rY3Ko1I4q-HjgIO3PPq5URB0RPr1RwqP', + 'workflowId', + ], + }, + }, + }, + { + id: 'HjgIO3PPq5URB0RPr1Rwmv', + timestamp: '2025-02-17T15:37:41.186908Z', + author: 'p8xDvUMxC', + + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [ + 11, + 3, + 23, + 0, + 17, + 22, + '41', + 23, + 19, + 20, + 15, + 17, + 'archiving', + 'state', + 17, + 'p8xDvUMxC', + 'userId', + 17, + 'release-rY3Ko1I4q-HjgIO3PPq5URB0RPr1RwqP', + 'workflowId', + ], + revert: [19, 7, 19, 8, 10, 0, 14, '_updatedAt', 17, 'active', 'state'], + }, + }, + }, + { + id: 'g9WA6RAm4T2Bl8zZYz08dP', + timestamp: '2025-02-17T15:37:30.977183Z', + author: 'p8xDvUMxC', + documentIDs: ['_.releases.rY3Ko1I4q'], + effects: { + '_.releases.rY3Ko1I4q': { + apply: [ + 0, + { + _createdAt: '2025-02-17T15:37:30Z', + _id: '_.releases.rY3Ko1I4q', + _type: 'system.release', + _updatedAt: '2025-02-17T15:37:30Z', + metadata: { + description: '', + releaseType: 'asap', + title: 'To be archived', + }, + name: 'rY3Ko1I4q', + state: 'active', + }, + ], + revert: [0, null], + }, + }, + }, + ], +} diff --git a/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.test.ts b/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.test.ts index 16e87a31688..e8ea7eccff0 100644 --- a/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.test.ts +++ b/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.test.ts @@ -1,6 +1,7 @@ import {describe, expect, it} from 'vitest' import {type ReleaseDocument} from '../../../store/types' +import {RELEASE_STATE_CHANGE} from './__fixtures__/mock-translog-events' import {buildReleaseEditEvents} from './buildReleaseEditEvents' describe('buildReleaseEditEvents()', () => { @@ -574,4 +575,46 @@ describe('buildReleaseEditEvents()', () => { }, ]) }) + it('should identify state changes', () => { + const changes = buildReleaseEditEvents( + RELEASE_STATE_CHANGE.transactions, + RELEASE_STATE_CHANGE.document, + ) + + expect(changes).toEqual([ + { + type: 'publishRelease', + timestamp: '2025-02-17T15:50:22.301295Z', + author: 'p8xDvUMxC', + releaseName: 'rY3Ko1I4q', + id: 'ad56vrBQ94RnZtJ3uXY2W4', + origin: 'translog', + }, + { + type: 'unarchiveRelease', + timestamp: '2025-02-17T15:48:37.430863Z', + author: 'p8xDvUMxC', + releaseName: 'rY3Ko1I4q', + id: 'ad56vrBQ94RnZtJ3uXY2N4', + origin: 'translog', + }, + { + type: 'archiveRelease', + timestamp: '2025-02-17T15:37:41.591300Z', + author: 'p8xDvUMxC', + releaseName: 'rY3Ko1I4q', + id: 'ad56vrBQ94RnZtJ3uXY2K4', + origin: 'translog', + }, + { + type: 'createRelease', + origin: 'translog', + author: 'p8xDvUMxC', + change: {releaseType: 'asap'}, + id: 'g9WA6RAm4T2Bl8zZYz08dP', + timestamp: '2025-02-17T15:37:30.977183Z', + releaseName: 'rY3Ko1I4q', + }, + ]) + }) }) diff --git a/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.ts b/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.ts index 750512c24c6..fb5b4fa4286 100644 --- a/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.ts +++ b/packages/sanity/src/core/releases/tool/detail/events/buildReleaseEditEvents.ts @@ -3,19 +3,19 @@ import {type TransactionLogEventWithEffects} from '@sanity/types' import {applyMendozaPatch} from '../../../../preview/utils/applyMendozaPatch' import {type ReleaseDocument, type ReleaseType} from '../../../store/types' import {getReleaseIdFromReleaseDocumentId} from '../../../util/getReleaseIdFromReleaseDocumentId' -import {type CreateReleaseEvent, type EditReleaseEvent} from './types' +import {type ReleaseEvent} from './types' export function buildReleaseEditEvents( transactions: TransactionLogEventWithEffects[], release: ReleaseDocument, -): (EditReleaseEvent | CreateReleaseEvent)[] { +): ReleaseEvent[] { // Confirm we have all the events by checking the first transaction id and the release._rev, the should match. if (release._rev !== transactions[0]?.id) { console.error('Some transactions are missing, cannot calculate the edit events') return [] } - const releaseEditEvents: (EditReleaseEvent | CreateReleaseEvent)[] = [] + const releaseEditEvents: ReleaseEvent[] = [] // We start from the last release document and apply changes in reverse order // Compare for each transaction what changed, if metadata.releaseType or metadata.intendedPublishAt changed build an event. let currentDocument = release @@ -29,6 +29,37 @@ export function buildReleaseEditEvents( intendedPublishDate?: string } = {} + if (before?.state !== currentDocument.state && currentDocument.state === 'archived') { + releaseEditEvents.push({ + type: 'archiveRelease', + timestamp: transaction.timestamp, + author: transaction.author, + releaseName: getReleaseIdFromReleaseDocumentId(release._id), + id: transaction.id, + origin: 'translog', + }) + } + if (before?.state !== currentDocument.state && currentDocument.state === 'published') { + releaseEditEvents.push({ + type: 'publishRelease', + timestamp: transaction.timestamp, + author: transaction.author, + releaseName: getReleaseIdFromReleaseDocumentId(release._id), + id: transaction.id, + origin: 'translog', + }) + } + + if (before?.state === 'unarchiving' && currentDocument.state === 'active') { + releaseEditEvents.push({ + type: 'unarchiveRelease', + timestamp: transaction.timestamp, + author: transaction.author, + releaseName: getReleaseIdFromReleaseDocumentId(release._id), + id: transaction.id, + origin: 'translog', + }) + } if (before?.metadata.releaseType !== currentDocument.metadata.releaseType) { changed.releaseType = currentDocument.metadata.releaseType } @@ -46,9 +77,10 @@ export function buildReleaseEditEvents( timestamp: transaction.timestamp, releaseName: getReleaseIdFromReleaseDocumentId(release._id), }) - if (before) { - currentDocument = before - } + } + + if (before) { + currentDocument = before } } return releaseEditEvents diff --git a/packages/sanity/src/core/releases/tool/detail/events/getReleaseEditEvents.ts b/packages/sanity/src/core/releases/tool/detail/events/getReleaseEditEvents.ts index 3ecdd369592..152b4c589db 100644 --- a/packages/sanity/src/core/releases/tool/detail/events/getReleaseEditEvents.ts +++ b/packages/sanity/src/core/releases/tool/detail/events/getReleaseEditEvents.ts @@ -20,7 +20,7 @@ import { import {getTransactionsLogs} from '../../../../store/translog/getTransactionsLogs' import {type ReleaseDocument} from '../../../store/types' import {buildReleaseEditEvents} from './buildReleaseEditEvents' -import {type CreateReleaseEvent, type EditReleaseEvent} from './types' +import {type ReleaseEvent} from './types' const TRANSLOG_ENTRY_LIMIT = 100 @@ -108,7 +108,7 @@ function getReleaseTransactions({ } interface EditEventsObservableValue { - editEvents: (EditReleaseEvent | CreateReleaseEvent)[] + editEvents: ReleaseEvent[] loading: boolean error: null | Error } diff --git a/packages/sanity/src/core/releases/tool/detail/events/getReleaseEvents.ts b/packages/sanity/src/core/releases/tool/detail/events/getReleaseEvents.ts index fba73f3e588..f5584ff36b0 100644 --- a/packages/sanity/src/core/releases/tool/detail/events/getReleaseEvents.ts +++ b/packages/sanity/src/core/releases/tool/detail/events/getReleaseEvents.ts @@ -116,7 +116,7 @@ export function getReleaseEvents({ }, []) return { - events, + events: deduplicateEvents(events), hasMore: Boolean(activity.nextCursor), error: activity.error || edit.error, loading: activity.loading || edit.loading, @@ -129,3 +129,22 @@ export function getReleaseEvents({ loadMore: activityEvents.loadMore, } } + +const deduplicateEvents = (events: ReleaseEvent[]) => { + // Events are sorted by timestamp, compare this event with the next one, if they are the same type and timestamp, remove it. + return events.filter((event, index) => { + const nextEvent = events[index + 1] + if (!nextEvent) return true + return !(event.type === nextEvent.type && areTheSameDate(event.timestamp, nextEvent.timestamp)) + }) +} + +/** + * Checks if two dates are the same date, without contemplating miliseconds. + * The translog has miliseconds but the events api does not. + */ +function areTheSameDate(date1: string, date2: string) { + const time1 = new Date(date1).getTime() + const time2 = new Date(date2).getTime() + return Math.abs(time1 - time2) <= 1000 +}