Skip to content

Commit

Permalink
wip -- add document events story, update timeline items
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrobonamin committed Oct 3, 2024
1 parent 0a11216 commit 2841ec0
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 135 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/* eslint-disable react/jsx-no-bind */
import {type TransactionLogEventWithEffects} from '@sanity/types'
import {Box, Card, Code, Container, Flex, Text, useToast} from '@sanity/ui'
import {useString} from '@sanity/ui-workshop'
import {useMemo, useState} from 'react'
import {useObservable} from 'react-rx'
import {map, type Observable, of, startWith} from 'rxjs'
import {getDraftId, getPublishedId, useClient} from 'sanity'

import {Timeline} from '../../../../structure/panes/document/timeline/timeline'
import {Button} from '../../../../ui-components'
import {getDocumentEvents} from '../getDocumentEvents'
import {type DocumentGroupEvent} from '../types'

const query = {
excludeContent: 'true',
includeIdentifiedDocumentsOnly: 'true',
tag: 'sanity.studio.structure.transactions',
effectFormat: 'mendoza',
excludeMutations: 'true',
reverse: 'true',
limit: '50',
}

export default function DocumentEvents() {
const documentId = useString('Document Id', '') || ''
const client = useClient()
const [selectedEventId, setSelectedEventId] = useState<string>('')
const transactions$: Observable<TransactionLogEventWithEffects[]> = useMemo(() => {
const dataset = client.config().dataset
if (!documentId) return of([])

const ids = [getPublishedId(documentId), getDraftId(documentId)]
return client.observable
.request({url: `/data/history/${dataset}/transactions/${ids.join(',')}?`, query})
.pipe(
map((result) =>
result
.toString('utf8')
.split('\n')
.filter(Boolean)
.map((line: string) => JSON.parse(line)),
),
startWith([]),
)
}, [client, documentId])

const transactions = useObservable(transactions$, [])
const events = getDocumentEvents(getPublishedId(documentId), transactions)
const selectedEvent = events.find((e) => e.id === selectedEventId)
const selectedEvents = useMemo(() => {
if (!selectedEventId) return []
const event = events.find((e) => {
if (e.type === 'document.editVersion' && e.mergedEvents) {
// See if the selected event is a merged event
return e.id === selectedEventId || e.mergedEvents.some((me) => me.id === selectedEventId)
}
return e.id === selectedEventId
})
if (event?.type === 'document.editVersion') {
return [event.id, event.mergedEvents?.map((e) => e.id)].flat()
}
return [event?.id].filter(Boolean)
}, [events, selectedEventId])

const handleSelectItem = (event: DocumentGroupEvent) => {
setSelectedEventId(event.id)
// Find the node with `event.id` and scroll it into view
const eventNode = document.getElementById(`event-${event.id}`)
if (eventNode) {
eventNode.scrollIntoView({behavior: 'instant', block: 'start'})
}

setTimeout(() => {
const transactionNode = document.getElementById(`transaction-${event.id}`)
if (transactionNode) {
transactionNode.scrollIntoView({behavior: 'instant', block: 'start'})
}
}, 50)
}

const toast = useToast()
return (
<Box
style={{
height: 'calc(100% - 8px)',
padding: '4px',
}}
>
<Flex gap={3} direction="column" height="fill">
<Card flex={1} border height={'fill'}>
<Flex flex={1} height={'fill'}>
<Container width={0} overflow="auto" height={'fill'}>
<Flex direction={'column'} flex={1} gap={3} padding={3}>
<Text size={2} weight="semibold">
History Timeline Items
</Text>

<Timeline
chunks={events}
hasMoreChunks={false}
lastChunk={selectedEvent}
onSelect={handleSelectItem}
onLoadMore={() => {}}
/>
</Flex>
</Container>

<Card flex={1} borderLeft padding={3} overflow="auto">
<Flex direction={'column'} flex={1}>
<Flex paddingBottom={3} align={'center'} justify={'space-between'}>
<Text size={2} weight="semibold">
Events
</Text>
<Button
mode="ghost"
onClick={() => {
// Copy to clipboard the events
navigator.clipboard.writeText(JSON.stringify(events, null, 2))
toast.push({
closable: true,
status: 'success',
title: 'Events copied to clipboard',
})
}}
text="Copy to clipboard"
/>
</Flex>
<Text size={0}>{events.length > 0 ? '[' : null}</Text>
{events.map((event) => {
return (
<Box
padding={1}
key={event.id}
id={`event-${event.id}`}
onMouseEnter={() => setSelectedEventId(event.id)}
style={{
border: selectedEvents.includes(event.id)
? '1px solid red'
: '1px solid transparent',
}}
>
<Code size={0} type="js" key={event.id}>
{JSON.stringify(event, null, 2)},
</Code>
</Box>
)
})}
<Text size={0}>{events.length > 0 ? ']' : null}</Text>
</Flex>
</Card>

<Card flex={1} borderLeft padding={3} overflow="auto">
<Flex paddingBottom={3} align={'center'} justify={'space-between'}>
<Text size={2} weight="semibold">
Transactions
</Text>
<Button
mode="ghost"
onClick={() => {
// Copy to clipboard the transactions
navigator.clipboard.writeText(JSON.stringify(transactions, null, 2))
toast.push({
closable: true,
status: 'success',
title: 'Transactions copied to clipboard',
})
}}
text="Copy to clipboard"
/>
</Flex>
<Text size={0}>{transactions.length > 0 ? '[' : null}</Text>
{transactions.map((transaction) => {
return (
<Box
padding={1}
key={transaction.id}
id={`transaction-${transaction.id}`}
onMouseEnter={() => setSelectedEventId(transaction.id)}
style={{
border: selectedEvents.includes(transaction.id)
? '1px solid red'
: '1px solid transparent',
}}
>
<Code size={0} key={transaction.id}>
{JSON.stringify(transaction, null, 2)},
</Code>
</Box>
)
})}
<Text size={0}>{transactions.length > 0 ? ']' : null}</Text>
</Card>
</Flex>
</Card>
</Flex>
</Box>
)
}
14 changes: 14 additions & 0 deletions packages/sanity/src/core/store/events/__workshop__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {defineScope} from '@sanity/ui-workshop'
import {lazy} from 'react'

export default defineScope({
name: 'core/store/document-group-events',
title: 'Documents group events',
stories: [
{
name: 'get-document-events',
title: 'Document group events',
component: lazy(() => import('./DocumentGroupEvent')),
},
],
})
28 changes: 14 additions & 14 deletions packages/sanity/src/core/store/events/getDocumentEvents.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {describe, expect, it} from '@jest/globals'
import {type TransactionLogEventWithEffects} from '@sanity/types'

import {getDocumentEvents} from './getDocumentEvents'
import {
Expand All @@ -9,7 +10,6 @@ import {
type DocumentGroupEvent,
type EditDocumentVersionEvent,
type PublishDocumentVersionEvent,
type Transaction,
type UpdateLiveDocumentEvent,
} from './types'

Expand Down Expand Up @@ -56,7 +56,7 @@ describe('getDocumentEvents', () => {
})
describe('document.editVersion ', () => {
it('edits an existing draft', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'edit-draft-tx-1',
timestamp: '2024-10-01T08:20:39.328125Z',
Expand Down Expand Up @@ -84,7 +84,7 @@ describe('getDocumentEvents', () => {
})
it('edits an existing draft multiple times within the time window, they are grouped', () => {
// TODO: Confirm this is the expected behavior
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'edit-draft-tx-3',
timestamp: '2024-10-01T08:20:40.759147Z',
Expand Down Expand Up @@ -250,7 +250,7 @@ describe('getDocumentEvents', () => {
])
})
it('deletes a draft (discard changes), published version exists', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'discard-changes-tx',
timestamp: '2024-09-30T16:04:31.096045Z',
Expand Down Expand Up @@ -401,7 +401,7 @@ describe('getDocumentEvents', () => {
describe('document.publishVersion', () => {
describe('draft version', () => {
it('publishes a draft', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'publish-tx',
timestamp: '2024-09-30T14:00:55.540022Z',
Expand Down Expand Up @@ -517,7 +517,7 @@ describe('getDocumentEvents', () => {
})
describe('document.unpublish', () => {
it('unpublishes a document, no draft exists', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'unpublish-tx',
timestamp: '2024-09-30T14:40:02.837538Z',
Expand Down Expand Up @@ -643,7 +643,7 @@ describe('getDocumentEvents', () => {
])
})
it('unpublishes a document, draft exists', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'unpublish-document-tx',
timestamp: '2024-09-30T15:04:37.077740Z',
Expand Down Expand Up @@ -842,7 +842,7 @@ describe('getDocumentEvents', () => {
it('deletes a group - only published doc exists', () => {
// TODO: How to distinguish this from from a unpublish transaction if the draft exists.
// They do the same type of operation given the draft is "unedited" it'
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'NQAO7ykovR2JyvCJEXET8v',
timestamp: '2024-10-01T09:13:02.083217Z',
Expand Down Expand Up @@ -924,7 +924,7 @@ describe('getDocumentEvents', () => {
it('deletes a group - only draft doc exists', () => {
// TODO: Confirm we want to have a type: document.deleteVersion in this case
// This uses the discard action
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'NQAO7ykovR2JyvCJEXQZNp',
timestamp: '2024-10-01T10:19:35.130918Z',
Expand Down Expand Up @@ -1022,7 +1022,7 @@ describe('getDocumentEvents', () => {

describe('document.createLive', () => {
it('creates a live document', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'create-live-doc-tx',
timestamp: '2024-09-30T16:15:06.436356Z',
Expand Down Expand Up @@ -1059,7 +1059,7 @@ describe('getDocumentEvents', () => {
})
describe('document.updateLive', () => {
it('updates a live document', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'update-live-doc-tx',
timestamp: '2024-09-30T16:22:37.797887Z',
Expand Down Expand Up @@ -1117,7 +1117,7 @@ describe('getDocumentEvents', () => {
})
describe('a long chain of transactions, imitating documents lifecycle', () => {
it('creates a draft document, adds some edits, publishes the document, updates the draft and publishes again, then the group is removed', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: '7X3uqAgvtaInRcPnekUfOB',
timestamp: '2024-10-01T13:50:40.265737Z',
Expand Down Expand Up @@ -1558,7 +1558,7 @@ describe('getDocumentEvents', () => {
expect(events).toEqual(expectedEvents)
})
it('creates a draft document, adds some edits, publishes the doc, then the document is unpublished, draft is removed, finally draft is restored', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: 'NQAO7ykovR2JyvCJEYUfaz',
timestamp: '2024-10-01T13:57:18.716920Z',
Expand Down Expand Up @@ -1786,7 +1786,7 @@ describe('getDocumentEvents', () => {
expect(events).toEqual(expectedEvents)
})
it('creates a live editable document and do edits on it, then it is removed', () => {
const transactions: Transaction[] = [
const transactions: TransactionLogEventWithEffects[] = [
{
id: '7X3uqAgvtaInRcPnekknt5',
timestamp: '2024-10-01T14:18:42.658609Z',
Expand Down
14 changes: 9 additions & 5 deletions packages/sanity/src/core/store/events/getDocumentEvents.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {type MendozaEffectPair, type MendozaPatch} from '@sanity/types'
import {
type MendozaEffectPair,
type MendozaPatch,
type TransactionLogEventWithEffects,
} from '@sanity/types'

import {getDraftId, getPublishedId, getVersionFromId} from '../../util/draftUtils'
import {type DocumentGroupEvent, documentVersionEventTypes, type Transaction} from './types'
import {type DocumentGroupEvent, documentVersionEventTypes} from './types'

type EffectState = 'unedited' | 'deleted' | 'upsert' | 'created'

Expand Down Expand Up @@ -91,8 +95,8 @@ function isDeletePatch(patch: MendozaPatch): boolean {
// This assumes the view is from the publishedDocument having only drafts. (Versions are not yet supported here)
function getEventFromTransaction(
documentId: string,
transaction: Transaction,
previousTransactions: Transaction[],
transaction: TransactionLogEventWithEffects,
previousTransactions: TransactionLogEventWithEffects[],
): DocumentGroupEvent | null {
const base = {
id: transaction.id,
Expand Down Expand Up @@ -306,7 +310,7 @@ const isDocumentGroupEvent = (event: unknown): event is DocumentGroupEvent => {
*/
export function getDocumentEvents(
documentId: string,
transactions: Transaction[],
transactions: TransactionLogEventWithEffects[],
): DocumentGroupEvent[] {
const events = transactions
.map((transaction, index) => {
Expand Down
Loading

0 comments on commit 2841ec0

Please sign in to comment.