diff --git a/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/ReleaseRevertButton.tsx b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/ReleaseRevertButton.tsx index 069f75caea39..d42b9153ed83 100644 --- a/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/ReleaseRevertButton.tsx +++ b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/ReleaseRevertButton.tsx @@ -1,7 +1,7 @@ import {RestoreIcon} from '@sanity/icons' import {useTelemetry} from '@sanity/telemetry/react' import {Box, Card, Checkbox, Flex, Text, useToast} from '@sanity/ui' -import {useCallback, useEffect, useMemo, useState} from 'react' +import {useCallback, useEffect, useState} from 'react' import {useRouter} from '../../../../../../router' import {Button} from '../../../../../../ui-components/button/Button' @@ -15,6 +15,7 @@ import {createReleaseId} from '../../../../util/createReleaseId' import {getReleaseIdFromReleaseDocumentId} from '../../../../util/getReleaseIdFromReleaseDocumentId' import {type DocumentInRelease} from '../../../detail/useBundleDocuments' import {useAdjacentTransactions} from './useAdjacentTransactions' +import {usePostPublishTransactions} from './usePostPublishTransactions' interface ReleasePublishAllButtonProps { release: ReleaseDocument @@ -22,20 +23,99 @@ interface ReleasePublishAllButtonProps { disabled?: boolean } +type RevertReleaseStatus = 'idle' | 'confirm' | 'reverting' + +const ConfirmReleaseDialog = ({ + revertReleaseStatus, + documents, + setRevertReleaseStatus, + release, + handleRevertRelease, +}: { + revertReleaseStatus: RevertReleaseStatus + documents: DocumentInRelease[] + setRevertReleaseStatus: (status: RevertReleaseStatus) => void + release: ReleaseDocument + handleRevertRelease: (stageNewRevertRelease: boolean) => Promise +}) => { + const {t} = useTranslation(releasesLocaleNamespace) + const hasPostPublishTransactions = usePostPublishTransactions(documents) + const [stageNewRevertRelease, setStageNewRevertRelease] = useState(true) + + if (revertReleaseStatus === 'idle') return null + + const description = + documents.length > 1 + ? 'revert-dialog.confirm-revert-description_other' + : 'revert-dialog.confirm-revert-description_one' + + return ( + setRevertReleaseStatus('idle')} + footer={{ + confirmButton: { + text: t( + stageNewRevertRelease + ? 'action.create-revert-release' + : 'action.immediate-revert-release', + ), + tone: 'positive', + onClick: () => handleRevertRelease(stageNewRevertRelease), + loading: revertReleaseStatus === 'reverting', + disabled: revertReleaseStatus === 'reverting', + }, + }} + > + + { + + } + + + setStageNewRevertRelease((current) => !current)} + id="stage-release" + style={{display: 'block'}} + checked={stageNewRevertRelease} + /> + + + + + + + {hasPostPublishTransactions && !stageNewRevertRelease && ( + + + {t('revert-dialog.confirm-revert.warning-card')} + + + )} + + ) +} + export const ReleaseRevertButton = ({ release, documents, disabled, }: ReleasePublishAllButtonProps) => { - const {hasPostPublishTransactions, getAdjacentTransactions} = useAdjacentTransactions(documents) + const {getAdjacentTransactions} = useAdjacentTransactions(documents) const {t} = useTranslation(releasesLocaleNamespace) - const [revertReleaseStatus, setRevertReleaseStatus] = useState<'idle' | 'confirm' | 'reverting'>( - 'idle', - ) + const [revertReleaseStatus, setRevertReleaseStatus] = useState('idle') const toast = useToast() const router = useRouter() const telemetry = useTelemetry() - const [stageNewRevertRelease, setStageNewRevertRelease] = useState(true) const {createRelease, publishRelease, createVersion} = useReleaseOperations() useEffect(() => { @@ -48,180 +128,114 @@ export const ReleaseRevertButton = ({ [router], ) - const handleRevertRelease = useCallback(async () => { - setRevertReleaseStatus('reverting') - const {documentRevertStates} = await getAdjacentTransactions() - - const revertReleaseId = createReleaseId() + const handleRevertRelease = useCallback( + async (stageNewRevertRelease: boolean) => { + setRevertReleaseStatus('reverting') + const documentRevertStates = await getAdjacentTransactions() - try { - if (!documentRevertStates) { - throw new Error('Unable to find documents to revert') - } + const revertReleaseId = createReleaseId() - await createRelease({ - _id: revertReleaseId, - metadata: { - title: t('revert-release.title', {title: release.metadata.title}), - description: t('revert-release.description', {title: release.metadata.title}), - releaseType: 'asap', - }, - }) + try { + if (!documentRevertStates) { + throw new Error('Unable to find documents to revert') + } - await Promise.allSettled( - documentRevertStates.map((document) => - createVersion(getReleaseIdFromReleaseDocumentId(revertReleaseId), document._id, document), - ), - ) + await createRelease({ + _id: revertReleaseId, + metadata: { + title: t('revert-release.title', {title: release.metadata.title}), + description: t('revert-release.description', {title: release.metadata.title}), + releaseType: 'asap', + }, + }) - if (stageNewRevertRelease) { - telemetry.log(RevertRelease, {revertType: 'staged'}) - toast.push({ - closable: true, - status: 'success', - title: ( - - ( - - {t('toast.revert-stage.success-link')} - - ), - }} - t={t} - i18nKey="toast.revert-stage.success" - values={{title: release.metadata.title}} - /> - + await Promise.allSettled( + documentRevertStates.map((document) => + createVersion( + getReleaseIdFromReleaseDocumentId(revertReleaseId), + document._id, + document, + ), ), - }) - } else { - await publishRelease(revertReleaseId) + ) + + if (stageNewRevertRelease) { + telemetry.log(RevertRelease, {revertType: 'staged'}) + toast.push({ + closable: true, + status: 'success', + title: ( + + ( + + {t('toast.revert-stage.success-link')} + + ), + }} + t={t} + i18nKey="toast.revert-stage.success" + values={{title: release.metadata.title}} + /> + + ), + }) + } else { + await publishRelease(revertReleaseId) - telemetry.log(RevertRelease, {revertType: 'immediately'}) + telemetry.log(RevertRelease, {revertType: 'immediately'}) + toast.push({ + closable: true, + status: 'success', + title: ( + + + + ), + }) + } + } catch (revertError) { toast.push({ - closable: true, - status: 'success', + status: 'error', title: ( - + ), }) + console.error(revertError) + } finally { + setRevertReleaseStatus('idle') } - } catch (revertError) { - toast.push({ - status: 'error', - title: ( - - - - ), - }) - console.error(revertError) - } finally { - setRevertReleaseStatus('idle') - } - }, [ - getAdjacentTransactions, - createRelease, - t, - release.metadata.title, - stageNewRevertRelease, - createVersion, - telemetry, - toast, - navigateToRevertRelease, - publishRelease, - ]) - - const confirmReleaseDialog = useMemo(() => { - if (revertReleaseStatus === 'idle') return null - - const description = - documents.length > 1 - ? 'revert-dialog.confirm-revert-description_other' - : 'revert-dialog.confirm-revert-description_one' - - return ( - setRevertReleaseStatus('idle')} - footer={{ - confirmButton: { - text: t( - stageNewRevertRelease - ? 'action.create-revert-release' - : 'action.immediate-revert-release', - ), - tone: 'positive', - onClick: handleRevertRelease, - loading: revertReleaseStatus === 'reverting', - disabled: revertReleaseStatus === 'reverting', - }, - }} - > - - { - - } - - - setStageNewRevertRelease((current) => !current)} - id="stage-release" - style={{display: 'block'}} - checked={stageNewRevertRelease} - /> - - - - - - - {hasPostPublishTransactions && !stageNewRevertRelease && ( - - - {t('revert-dialog.confirm-revert.warning-card')} - - - )} - - ) - }, [ - revertReleaseStatus, - documents.length, - t, - release.metadata.title, - stageNewRevertRelease, - handleRevertRelease, - hasPostPublishTransactions, - ]) + }, + [ + getAdjacentTransactions, + createRelease, + t, + release.metadata.title, + createVersion, + telemetry, + toast, + navigateToRevertRelease, + publishRelease, + ], + ) return ( <> @@ -232,7 +246,13 @@ export const ReleaseRevertButton = ({ tone="critical" disabled={disabled} /> - {confirmReleaseDialog} + ) } diff --git a/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/useAdjacentTransactions.ts b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/useAdjacentTransactions.ts index 96511d549543..709cf98b4e6b 100644 --- a/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/useAdjacentTransactions.ts +++ b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/useAdjacentTransactions.ts @@ -1,7 +1,7 @@ import {type SanityDocument} from '@sanity/types' import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {useObservable} from 'react-rx' -import {combineLatest, filter, forkJoin, from, map, type Observable, of, switchMap} from 'rxjs' +import {filter, forkJoin, from, map, type Observable, of, switchMap} from 'rxjs' import {useClient} from '../../../../../hooks/useClient' import {getTransactionsLogs} from '../../../../../store/translog/getTransactionLogs' @@ -17,7 +17,6 @@ type RevertDocument = SanityDocument & { type RevertDocuments = RevertDocument[] interface AdjacentTransactionsResult { - hasPostPublishTransactions: boolean | null documentRevertStates: RevertDocuments | null } @@ -27,36 +26,20 @@ export const useAdjacentTransactions = (documents: DocumentInRelease[]) => { const transactionId = documents[0]?.document._rev const {dataset} = client.config() const [trigger, setTrigger] = useState(0) - const promiseRef = useRef | null>(null) - const resolvePromiseRef = useRef<((value: AdjacentTransactionsResult) => void) | null>(null) - const resolvedValueRef = useRef(null) + const promiseRef = useRef | null>( + null, + ) + const resolvePromiseRef = useRef< + ((value: AdjacentTransactionsResult['documentRevertStates']) => void) | null + >(null) + const resolvedValueRef = useRef(null) const memoObservable = useMemo(() => { if (!trigger) { // If not triggered, return null to prevent the observable from running - return of({ - hasPostPublishTransactions: null, - documentRevertStates: null, - }) + return of(null) } - // Observable to check if there are post-publish transactions - const hasPostPublishTransactions$ = from( - getTransactionsLogs( - client, - documents.map(({document}) => document._id), - { - fromTransaction: transactionId, - // one transaction for every document plus the publish transaction - limit: 2, - }, - ), - ).pipe( - // the transaction of published is also returned - // so post publish transactions will result in more than 1 transaction - map((transactions) => transactions.length > 1), - ) - // Observable to get the revert states of the documents const documentRevertStates$: Observable = from( getTransactionsLogs( @@ -107,21 +90,13 @@ export const useAdjacentTransactions = (documents: DocumentInRelease[]) => { ), ) - return combineLatest([hasPostPublishTransactions$, documentRevertStates$]).pipe( - map(([hasPostPublishTransactions, documentRevertStates]) => ({ - hasPostPublishTransactions, - documentRevertStates, - })), - ) + return documentRevertStates$ }, [trigger, client, documents, transactionId, observableClient, dataset]) - const observableResult = useObservable(memoObservable, { - hasPostPublishTransactions: null, - documentRevertStates: null, - }) + const observableResult = useObservable(memoObservable, null) useEffect(() => { - if (observableResult.hasPostPublishTransactions !== null) { + if (observableResult !== null) { resolvedValueRef.current = observableResult // Resolve the promise if it exists @@ -149,8 +124,11 @@ export const useAdjacentTransactions = (documents: DocumentInRelease[]) => { return promiseRef.current }, []) - return { - ...observableResult, - getAdjacentTransactions: startObservables, - } + return useMemo( + () => ({ + documentRevertStates: observableResult, + getAdjacentTransactions: startObservables, + }), + [observableResult, startObservables], + ) } diff --git a/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/usePostPublishTransactions.ts b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/usePostPublishTransactions.ts new file mode 100644 index 000000000000..8cb0ee51301a --- /dev/null +++ b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleaseRevertButton/usePostPublishTransactions.ts @@ -0,0 +1,38 @@ +import {useMemo} from 'react' +import {useObservable} from 'react-rx' +import {from, map} from 'rxjs' + +import {useClient} from '../../../../../hooks/useClient' +import {getTransactionsLogs} from '../../../../../store/translog/getTransactionLogs' +import {API_VERSION} from '../../../../../tasks/constants' +import {type DocumentInRelease} from '../../../detail/useBundleDocuments' + +export const usePostPublishTransactions = (documents: DocumentInRelease[]) => { + const client = useClient({apiVersion: API_VERSION}) + const transactionId = documents[0]?.document._rev + + const memoObservable = useMemo(() => { + // Observable to check if there are post-publish transactions + const hasPostPublishTransactions$ = from( + getTransactionsLogs( + client, + documents.map(({document}) => document._id), + { + fromTransaction: transactionId, + // one transaction for every document plus the publish transaction + limit: 2, + }, + ), + ).pipe( + // the transaction of published is also returned + // so post publish transactions will result in more than 1 transaction + map((transactions) => transactions.length > 1), + ) + + return hasPostPublishTransactions$ + }, [client, documents, transactionId]) + + const observableResult = useObservable(memoObservable, null) + + return observableResult +}