Skip to content

Commit

Permalink
feat(sanity): add release layering foundations
Browse files Browse the repository at this point in the history
  • Loading branch information
juice49 committed Oct 24, 2024
1 parent 0f54d6b commit d8510f2
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import {styled} from 'styled-components'
import {useDateTimeFormat, useRelativeTime} from '../../hooks'
import {useTranslation} from '../../i18n'
import {type CurrentPerspective} from '../../releases'
import {type VersionsRecord} from '../../preview/utils/getPreviewStateObservable'
import {PerspectiveBadge} from '../perspective/PerspectiveBadge'

interface DocumentStatusProps {
absoluteDate?: boolean
draft?: PreviewValue | Partial<SanityDocument> | null
published?: PreviewValue | Partial<SanityDocument> | null
version?: PreviewValue | Partial<SanityDocument> | null
// eslint-disable-next-line
versions?: VersionsRecord
singleLine?: boolean
currentGlobalBundle?: CurrentPerspective
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import {Text} from '@sanity/ui'
import {useMemo} from 'react'
import {styled} from 'styled-components'

import {type VersionsRecord} from '../../preview/utils/getPreviewStateObservable'

interface DocumentStatusProps {
draft?: PreviewValue | Partial<SanityDocument> | null
published?: PreviewValue | Partial<SanityDocument> | null
version?: PreviewValue | Partial<SanityDocument> | null
// eslint-disable-next-line
versions?: VersionsRecord
}

const Root = styled(Text)`
Expand Down
73 changes: 61 additions & 12 deletions packages/sanity/src/core/preview/utils/getPreviewStateObservable.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import {type PreviewValue, type SanityDocument, type SchemaType} from '@sanity/types'
import {omit} from 'lodash'
import {type ReactNode} from 'react'
import {combineLatest, type Observable, of} from 'rxjs'
import {map, startWith} from 'rxjs/operators'
import {combineLatest, from, type Observable, of} from 'rxjs'
import {map, mergeMap, scan, startWith} from 'rxjs/operators'
import {type PreparedSnapshot} from 'sanity'

import {getDraftId, getPublishedId, getVersionId} from '../../util/draftUtils'
import {type DocumentPreviewStore} from '../documentPreviewStore'

/**
* @internal
*/
export type VersionsRecord = Record<string, PreparedSnapshot>

type VersionTuple = [bundleId: string, snapshot: PreparedSnapshot]

export interface PreviewState {
isLoading?: boolean
draft?: PreviewValue | Partial<SanityDocument> | null
published?: PreviewValue | Partial<SanityDocument> | null
version?: PreviewValue | Partial<SanityDocument> | null
versions: VersionsRecord
}

const isLiveEditEnabled = (schemaType: SchemaType) => schemaType.liveEdit === true
Expand All @@ -25,31 +35,70 @@ export function getPreviewStateObservable(
schemaType: SchemaType,
documentId: string,
title: ReactNode,
perspective?: string,
perspective: {
bundleIds: string[]
bundleStack: string[]
} = {
bundleIds: [],
bundleStack: [],
},
): Observable<PreviewState> {
const draft$ = isLiveEditEnabled(schemaType)
? of({snapshot: null})
: documentPreviewStore.observeForPreview({_id: getDraftId(documentId)}, schemaType)

const version$ = perspective
? documentPreviewStore.observeForPreview(
{_id: getVersionId(documentId, perspective)},
schemaType,
)
: of({snapshot: null})
const versions$ = from(perspective.bundleIds).pipe(
mergeMap<string, Observable<VersionTuple>>((bundleId) =>
documentPreviewStore
.observeForPreview({_id: getVersionId(documentId, bundleId)}, schemaType)
.pipe(map((storeValue) => [bundleId, storeValue])),
),
scan<VersionTuple, VersionsRecord>((byBundleId, [bundleId, value]) => {
if (value.snapshot === null) {
return omit({...byBundleId}, [bundleId])
}

return {
...byBundleId,
[bundleId]: value,
}
}, {}),
startWith<VersionsRecord>({}),
)

// Iterate the release stack in descending precedence, returning the highest precedence existing
// version document.
const version$ = versions$.pipe(
map((versions) => {
for (const bundleId of perspective.bundleStack) {
if (bundleId in versions) {
return versions[bundleId]
}
}
return {snapshot: null}
}),
startWith<PreparedSnapshot>({snapshot: null}),
)

const published$ = documentPreviewStore.observeForPreview(
{_id: getPublishedId(documentId)},
schemaType,
)

return combineLatest([draft$, published$, version$]).pipe(
map(([draft, published, version]) => ({
return combineLatest([draft$, published$, version$, versions$]).pipe(
map(([draft, published, version, versions]) => ({
draft: draft.snapshot ? {title, ...(draft.snapshot || {})} : null,
isLoading: false,
published: published.snapshot ? {title, ...(published.snapshot || {})} : null,
version: version.snapshot ? {title, ...(version.snapshot || {})} : null,
versions,
})),
startWith({draft: null, isLoading: true, published: null, version: null}),
startWith({
draft: null,
isLoading: true,
published: null,
version: null,
versions: {},
}),
)
}
10 changes: 7 additions & 3 deletions packages/sanity/src/core/store/_legacy/datastores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {useTelemetry} from '@sanity/telemetry/react'
import {useCallback, useMemo} from 'react'
import {of} from 'rxjs'

import {useRouter} from '../../../router'
import {useClient, useSchema, useTemplates} from '../../hooks'
import {createDocumentPreviewStore, type DocumentPreviewStore} from '../../preview'
import {useSource, useWorkspace} from '../../studio'
Expand Down Expand Up @@ -298,24 +299,27 @@ export function useReleasesStore(): ReleaseStore {
const workspace = useWorkspace()
const currentUser = useCurrentUser()
const studioClient = useClient(DEFAULT_STUDIO_CLIENT_OPTIONS)
const router = useRouter()

// TODO: Include hidden layers state.
return useMemo(() => {
const releaseStore =
resourceCache.get<ReleaseStore>({
dependencies: [workspace, currentUser],
dependencies: [workspace, currentUser, router.perspectiveState],
namespace: 'ReleasesStore',
}) ||
createReleaseStore({
client: studioClient,
currentUser,
perspective: router.perspectiveState.perspective,
})

resourceCache.set({
dependencies: [workspace, currentUser],
dependencies: [workspace, currentUser, router.perspectiveState],
namespace: 'ReleasesStore',
value: releaseStore,
})

return releaseStore
}, [resourceCache, workspace, studioClient, currentUser])
}, [resourceCache, workspace, studioClient, currentUser, router.perspectiveState])
}
4 changes: 3 additions & 1 deletion packages/sanity/src/core/store/release/createReleaseStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const INITIAL_STATE: ReleasesReducerState = {
releases: new Map(),
deletedReleases: {},
state: 'loaded' as const,
releaseStack: [],
}

/**
Expand All @@ -70,6 +71,7 @@ const INITIAL_STATE: ReleasesReducerState = {
export function createReleaseStore(context: {
client: SanityClient
currentUser: User | null
perspective?: string
}): ReleaseStore {
const {client, currentUser} = context

Expand Down Expand Up @@ -219,7 +221,7 @@ export function createReleaseStore(context: {

const state$ = merge(listFetch$, listener$, dispatch$).pipe(
filter((action): action is ReleasesReducerAction => typeof action !== 'undefined'),
scan((state, action) => releasesReducer(state, action), INITIAL_STATE),
scan((state, action) => releasesReducer(state, action, context.perspective), INITIAL_STATE),
startWith(INITIAL_STATE),
shareReplay(1),
)
Expand Down
57 changes: 57 additions & 0 deletions packages/sanity/src/core/store/release/reducer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {DRAFTS_FOLDER, resolveBundlePerspective} from 'sanity'

import {type ReleaseDocument} from './types'

interface BundleDeletedAction {
Expand Down Expand Up @@ -42,6 +44,12 @@ export interface ReleasesReducerState {
deletedReleases: Record<string, ReleaseDocument>
state: 'initialising' | 'loading' | 'loaded' | 'error'
error?: Error

/**
* An array of release ids ordered chronologically to represent the state of documents at the
* given point in time.
*/
releaseStack: string[]
}

function createReleasesSet(releases: ReleaseDocument[] | null) {
Expand All @@ -54,6 +62,7 @@ function createReleasesSet(releases: ReleaseDocument[] | null) {
export function releasesReducer(
state: ReleasesReducerState,
action: ReleasesReducerAction,
perspective?: string,
): ReleasesReducerState {
switch (action.type) {
case 'LOADING_STATE_CHANGED': {
Expand All @@ -71,6 +80,10 @@ export function releasesReducer(
return {
...state,
releases: releasesById,
releaseStack: getReleaseStack({
releases: releasesById,
perspective,
}),
}
}

Expand All @@ -82,6 +95,10 @@ export function releasesReducer(
return {
...state,
releases: currentReleases,
releaseStack: getReleaseStack({
releases: currentReleases,
perspective,
}),
}
}

Expand All @@ -105,6 +122,7 @@ export function releasesReducer(
...state,
releases: currentReleases,
deletedReleases: nextDeletedReleases,
releaseStack: [...state.releaseStack].filter((id) => id !== deletedBundleId),
}
}

Expand All @@ -117,10 +135,49 @@ export function releasesReducer(
return {
...state,
releases: currentReleases,
releaseStack: getReleaseStack({
releases: currentReleases,
perspective,
}),
}
}

default:
return state
}
}

function getReleaseStack({
releases,
perspective,
}: {
releases?: Map<string, ReleaseDocument>
perspective?: string
}): string[] {
if (typeof releases === 'undefined') {
return []
}

// TODO: Handle system perspectives.
if (!perspective?.startsWith('bundle.')) {
return []
}

const stack = [...releases.values()]
.toSorted(sortReleases(resolveBundlePerspective(perspective)))
.map(({_id}) => _id)
.concat(DRAFTS_FOLDER)

return stack
}

// TODO: Implement complete layering heuristics.
function sortReleases(perspective?: string): (a: ReleaseDocument, b: ReleaseDocument) => number {
return function (a, b) {
// Ensure the current release takes highest precedence.
if (a._id === perspective) {
return -1
}
return 0
}
}
9 changes: 9 additions & 0 deletions packages/sanity/src/core/store/release/useReleases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import {type ReleaseDocument} from './types'

interface ReleasesState {
data: ReleaseDocument[]
releases: Map<string, ReleaseDocument>
deletedReleases: Record<string, ReleaseDocument>
error?: Error
loading: boolean
dispatch: React.Dispatch<ReleasesReducerAction>

/**
* An array of release ids ordered chronologically to represent the state of documents at the
* given point in time.
*/
stack: string[]
}

/**
Expand All @@ -24,9 +31,11 @@ export function useReleases(): ReleasesState {

return {
data: releasesAsArray,
releases: state.releases,
deletedReleases: deletedReleases,
dispatch,
error,
loading: ['loading', 'initialising'].includes(state.state),
stack: state.releaseStack,
}
}
Loading

0 comments on commit d8510f2

Please sign in to comment.