diff --git a/packages/sanity/src/core/releases/tool/detail/ReleaseDetail.tsx b/packages/sanity/src/core/releases/tool/detail/ReleaseDetail.tsx
index bbd447ada65..4219f338293 100644
--- a/packages/sanity/src/core/releases/tool/detail/ReleaseDetail.tsx
+++ b/packages/sanity/src/core/releases/tool/detail/ReleaseDetail.tsx
@@ -11,6 +11,7 @@ import {type BundleDocument} from '../../../store/bundles/types'
import {API_VERSION} from '../../../tasks/constants'
import {BundleMenuButton} from '../../components/BundleMenuButton/BundleMenuButton'
import {type ReleasesRouterState} from '../../types/router'
+import {useReleaseHistory} from './documentTable/useReleaseHistory'
import {ReleaseOverview} from './ReleaseOverview'
type Screen = 'overview' | 'review'
@@ -29,6 +30,8 @@ export const ReleaseDetail = () => {
const {data, loading} = useBundles()
const {documents: bundleDocuments, loading: documentsLoading} =
useFetchBundleDocuments(parsedBundleName)
+ const history = useReleaseHistory(bundleDocuments)
+
const bundle = data?.find((storeBundle) => storeBundle.name === parsedBundleName)
const bundleHasDocuments = !!bundleDocuments.length
const showPublishButton = loading || !bundle?.publishedAt
@@ -120,7 +123,12 @@ export const ReleaseDetail = () => {
) : (
<>
{activeScreen === 'overview' && (
-
+
)}
>
)}
diff --git a/packages/sanity/src/core/releases/tool/detail/ReleaseOverview.tsx b/packages/sanity/src/core/releases/tool/detail/ReleaseOverview.tsx
index 68bfc82deca..a52db886094 100644
--- a/packages/sanity/src/core/releases/tool/detail/ReleaseOverview.tsx
+++ b/packages/sanity/src/core/releases/tool/detail/ReleaseOverview.tsx
@@ -13,14 +13,17 @@ import {type BundleDocument} from '../../../store/bundles/types'
import {useAddonDataset} from '../../../studio/addonDataset/useAddonDataset'
import {Chip} from '../../components/Chip'
import {DocumentTable} from './documentTable'
+import {type DocumentHistory} from './documentTable/useReleaseHistory'
-export function ReleaseOverview(props: {documents: SanityDocument[]; release: BundleDocument}) {
- const {documents, release} = props
+export function ReleaseOverview(props: {
+ documents: SanityDocument[]
+ documentsHistory: Map
+ collaborators: string[]
+ release: BundleDocument
+}) {
+ const {documents, documentsHistory, release, collaborators} = props
const {client} = useAddonDataset()
- /**
- * This state is created here but will be updated by the DocumentRow component when fetching the history
- */
- const [collaborators, setCollaborators] = useState([])
+
const [iconValue, setIconValue] = useState({
hue: release.hue ?? 'gray',
icon: release.icon ?? 'documents',
@@ -132,7 +135,7 @@ export function ReleaseOverview(props: {documents: SanityDocument[]; release: Bu
)}
diff --git a/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentRow.tsx b/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentRow.tsx
index 133b9af28f7..a5444a2d03e 100644
--- a/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentRow.tsx
+++ b/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentRow.tsx
@@ -1,14 +1,7 @@
import {CheckmarkCircleIcon, EmptyIcon, Progress50Icon} from '@sanity/icons'
import {type SanityDocument} from '@sanity/types'
import {AvatarStack, Box, Card, Flex, Text} from '@sanity/ui'
-import {
- type Dispatch,
- type ForwardedRef,
- forwardRef,
- type SetStateAction,
- useEffect,
- useMemo,
-} from 'react'
+import {type ForwardedRef, forwardRef, useMemo} from 'react'
import {getPublishedId, RelativeTime, SanityDefaultPreview, UserAvatar} from 'sanity'
import {IntentLink} from 'sanity/router'
@@ -16,7 +9,7 @@ import {Tooltip} from '../../../../../ui-components'
import {type BundleDocument} from '../../../../store/bundles/types'
import {DocumentActions} from './DocumentActions'
import {useDocumentPreviewValues} from './useDocumentPreviewValues'
-import {useVersionHistory} from './useVersionHistory'
+import {type DocumentHistory} from './useReleaseHistory'
const DOCUMENT_STATUS = {
ready: {
@@ -63,19 +56,22 @@ export function DocumentRow(props: {
searchTerm: string
document: SanityDocument
release: BundleDocument
- setCollaborators: Dispatch>
+ history: DocumentHistory | undefined
}) {
- const {document, release, searchTerm, setCollaborators} = props
+ const {
+ document,
+ release,
+ searchTerm,
+ history = {
+ editors: [],
+ createdBy: undefined,
+ lastEditedBy: undefined,
+ },
+ } = props
const documentId = document._id
const documentTypeName = document._type
const {previewValues, isLoading} = useDocumentPreviewValues({document, release})
- const history = useVersionHistory(documentId, document?._rev)
-
- useEffect(() => {
- setCollaborators((pre) => Array.from(new Set([...pre, ...history.editors])))
- }, [history.editors, setCollaborators])
-
const LinkComponent = useMemo(
() =>
// eslint-disable-next-line @typescript-eslint/no-shadow
diff --git a/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentTable.tsx b/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentTable.tsx
index 7399ab9c674..8ae612b1673 100644
--- a/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentTable.tsx
+++ b/packages/sanity/src/core/releases/tool/detail/documentTable/DocumentTable.tsx
@@ -1,12 +1,13 @@
import {type SanityDocument} from '@sanity/types'
import {Stack} from '@sanity/ui'
-import {type Dispatch, type SetStateAction, useMemo, useState} from 'react'
+import {useMemo, useState} from 'react'
import {styled} from 'styled-components'
import {type BundleDocument} from '../../../../store/bundles/types'
import {DocumentHeader} from './DocumentHeader'
import {DocumentRow} from './DocumentRow'
import {type DocumentSort} from './types'
+import {type DocumentHistory} from './useReleaseHistory'
const RowStack = styled(Stack)({
'& > *:not(:first-child)': {
@@ -23,10 +24,10 @@ const RowStack = styled(Stack)({
export function DocumentTable(props: {
documents: SanityDocument[]
+ documentsHistory: Map
release: BundleDocument
- setCollaborators: Dispatch>
}) {
- const {documents, release, setCollaborators} = props
+ const {documents, release, documentsHistory} = props
// Filter will happen at the DocumentRow level because we don't have access here to the preview values.
const [searchTerm, setSearchTerm] = useState('')
const [sort, setSort] = useState({property: '_updatedAt', order: 'desc'})
@@ -66,7 +67,7 @@ export function DocumentTable(props: {
document={d}
key={d._id}
release={release}
- setCollaborators={setCollaborators}
+ history={documentsHistory.get(d._id)}
/>
))}
diff --git a/packages/sanity/src/core/releases/tool/detail/documentTable/useReleaseHistory.ts b/packages/sanity/src/core/releases/tool/detail/documentTable/useReleaseHistory.ts
new file mode 100644
index 00000000000..485aa23207c
--- /dev/null
+++ b/packages/sanity/src/core/releases/tool/detail/documentTable/useReleaseHistory.ts
@@ -0,0 +1,98 @@
+import {useCallback, useEffect, useMemo, useState} from 'react'
+import {
+ type BundleDocument,
+ getPublishedId,
+ type TransactionLogEventWithEffects,
+ useClient,
+} from 'sanity'
+
+import {getJsonStream} from '../../../../store/_legacy/history/history/getJsonStream'
+import {API_VERSION} from '../../../../tasks/constants'
+
+export type DocumentHistory = {
+ history: TransactionLogEventWithEffects[]
+ createdBy: string
+ lastEditedBy: string
+ editors: string[]
+}
+
+// TODO: Update this to contemplate the _revision change on any of the internal bundle documents, and fetch only the history of that document if changes.
+export function useReleaseHistory(bundleDocuments: BundleDocument[]): {
+ documentsHistory: Map
+ collaborators: string[]
+ loading: boolean
+} {
+ const client = useClient({apiVersion: API_VERSION})
+ const {dataset, token} = client.config()
+ const [history, setHistory] = useState([])
+ const queryParams = `tag=sanity.studio.tasks.history&effectFormat=mendoza&excludeContent=true&includeIdentifiedDocumentsOnly=true`
+ const bundleDocumentsIds = useMemo(() => bundleDocuments.map((doc) => doc._id), [bundleDocuments])
+
+ const publishedIds = bundleDocumentsIds.map((id) => getPublishedId(id)).join(',')
+ const transactionsUrl = client.getUrl(
+ `/data/history/${dataset}/transactions/${publishedIds}?${queryParams}`,
+ )
+
+ const fetchAndParseAll = useCallback(async () => {
+ if (!publishedIds) return
+ const transactions: TransactionLogEventWithEffects[] = []
+ const stream = await getJsonStream(transactionsUrl, token)
+ const reader = stream.getReader()
+ let result
+ for (;;) {
+ result = await reader.read()
+ if (result.done) {
+ break
+ }
+ if ('error' in result.value) {
+ throw new Error(result.value.error.description || result.value.error.type)
+ }
+ transactions.push(result.value)
+ }
+ setHistory(transactions)
+ }, [publishedIds, transactionsUrl, token])
+
+ useEffect(() => {
+ fetchAndParseAll()
+ // When revision changes, update the history.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [fetchAndParseAll])
+
+ return useMemo(() => {
+ const collaborators: string[] = []
+ const documentsHistory = new Map()
+ if (!history.length) {
+ return {documentsHistory, collaborators, loading: true}
+ }
+ history.forEach((item) => {
+ const documentId = item.documentIDs[0]
+ let documentHistory = documentsHistory.get(documentId)
+ if (!collaborators.includes(item.author)) {
+ collaborators.push(item.author)
+ }
+ // eslint-disable-next-line no-negated-condition
+ if (!documentHistory) {
+ documentHistory = {
+ history: [item],
+ createdBy: item.author,
+ lastEditedBy: item.author,
+ editors: [item.author],
+ }
+ documentsHistory.set(documentId, documentHistory)
+ } else {
+ // @ts-expect-error TransactionLogEventWithEffects has no property 'mutations' but it's returned from the API
+ const isCreate = item.mutations.some((mutation) => 'create' in mutation)
+ if (isCreate) documentHistory.createdBy = item.author
+ if (!documentHistory.editors.includes(item.author)) {
+ documentHistory.editors.push(item.author)
+ }
+ // The last item in the history is the last edited by, transaction log is ordered by timestamp
+ documentHistory.lastEditedBy = item.author
+ // always add history item
+ documentHistory.history.push(item)
+ }
+ })
+
+ return {documentsHistory, collaborators, loading: false}
+ }, [history])
+}
diff --git a/packages/sanity/src/core/releases/tool/detail/documentTable/useVersionHistory.ts b/packages/sanity/src/core/releases/tool/detail/documentTable/useVersionHistory.ts
deleted file mode 100644
index 2f701b82d1d..00000000000
--- a/packages/sanity/src/core/releases/tool/detail/documentTable/useVersionHistory.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import {useCallback, useEffect, useMemo, useState} from 'react'
-import {getPublishedId, type TransactionLogEventWithEffects, useClient} from 'sanity'
-
-import {getJsonStream} from '../../../../store/_legacy/history/history/getJsonStream'
-import {API_VERSION} from '../../../../tasks/constants'
-
-/**
- * TODO:
- * Temporal solution, will be replaced once we have the API endpoint that returns all the necessary data.
- */
-export function useVersionHistory(id: string, revision: string) {
- const client = useClient({apiVersion: API_VERSION})
- const {dataset, token} = client.config()
- const [history, setHistory] = useState([])
- const queryParams = `tag=sanity.studio.tasks.history&effectFormat=mendoza&excludeContent=true&includeIdentifiedDocumentsOnly=true`
- const publishedId = getPublishedId(id)
- const transactionsUrl = client.getUrl(
- `/data/history/${dataset}/transactions/${publishedId}?${queryParams}`,
- )
-
- const fetchAndParse = useCallback(async () => {
- const transactions: TransactionLogEventWithEffects[] = []
- const stream = await getJsonStream(transactionsUrl, token)
- const reader = stream.getReader()
- let result
- for (;;) {
- result = await reader.read()
- if (result.done) {
- break
- }
- if ('error' in result.value) {
- throw new Error(result.value.error.description || result.value.error.type)
- }
- transactions.push(result.value)
- setHistory(transactions)
- }
- }, [transactionsUrl, token])
-
- useEffect(() => {
- fetchAndParse()
- // When revision changes, update the history.
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [fetchAndParse, revision])
- const createdBy = history[0]?.author
- const lastEditedBy = history[history.length - 1]?.author
- const editors = useMemo(
- () => Array.from(new Set(history.map((event) => event.author).filter(Boolean))),
- [history],
- )
-
- return {history, createdBy, lastEditedBy, editors}
-}