Skip to content

Commit

Permalink
[preview] Emit null as snapshot for missing documents (#634)
Browse files Browse the repository at this point in the history
* [preview] Emit null values for missing/deleted documents

* [preview] Make sure changes are applied to snapshots of same revision
  • Loading branch information
bjoerge committed Mar 5, 2018
1 parent 582a5ee commit fd65783
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 18 deletions.
3 changes: 3 additions & 0 deletions packages/@sanity/preview/src/createPathObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ function observePaths(value: Value, paths: Path[], observeFields: ObserveFieldsF
if (isReference(value) || isDocument(value)) {
const id = isRef ? value._ref : value._id
return observeFields(id, nextHeads).switchMap(snapshot => {
if (snapshot === null) {
return Observable.of(null)
}
return observePaths(
{
...createEmpty(nextHeads),
Expand Down
38 changes: 23 additions & 15 deletions packages/@sanity/preview/src/observeFields.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {combineSelections, reassemble, toGradientQuery} from './utils/optimizeQu
import {flatten, difference} from 'lodash'
import type {FieldName, Id} from './types'
import {INCLUDE_FIELDS} from './constants'
import hasEqualFields from './utils/hasEqualFields'
import isUniqueBy from './utils/isUniqueBy'

let _globalListener
const getGlobalEvents = () => {
Expand Down Expand Up @@ -55,21 +57,18 @@ const fetchDocumentPathsFast = debounceCollect(fetchAllDocumentPaths, 100)
const fetchDocumentPathsSlow = debounceCollect(fetchAllDocumentPaths, 1000)

function listenFields(id: Id, fields: FieldName[]) {
return listen(id)
.switchMap(
event =>
event.type === 'welcome'
? fetchDocumentPathsFast(id, fields)
: fetchDocumentPathsSlow(id, fields)
)
.mergeMap(
result =>
result === undefined
return listen(id).switchMap(event => {
if (event.type === 'welcome') {
return fetchDocumentPathsFast(id, fields).mergeMap(result => {
return result === undefined
? // hack: if we get undefined as result here it is most likely because the document has
// just been created and is not yet indexed. We therefore need to wait a bit and then re-fetch.
fetchDocumentPathsSlow(id, fields)
: Observable.of(result)
)
})
}
return fetchDocumentPathsSlow(id, fields)
})
}

// keep for debugging purposes for now
Expand All @@ -96,7 +95,7 @@ function createCachedFieldObserver(id, fields): CachedFieldObserver {
.filter(Boolean)
.merge(listenFields(id, fields))
.do(v => (latest = v))
.publishReplay()
.publishReplay(1)
.refCount()

return {id, fields, changes$}
Expand All @@ -121,9 +120,18 @@ export default function cachedObserveFields(id: Id, fields: FieldName[]) {
.filter(observer => observer.fields.some(fieldName => fields.includes(fieldName)))
.map(cached => cached.changes$)

return Observable.combineLatest(cachedFieldObservers)
.map(snapshots => pickFrom(snapshots, fields))
.distinctUntilChanged((prev, current) => fields.every(field => prev[field] === current[field]))
return (
Observable.combineLatest(cachedFieldObservers)
// in the event that a document gets deleted, the cached values will be updated to store `undefined`
// if this happens, we should not pick any fields from it, but rather just return null
.map(snapshots => snapshots.filter(Boolean))
// make sure all snapshots agree on same revision
.filter(snapshots => isUniqueBy(snapshots, snapshot => snapshot._rev))
// pass on value with the requested fields (or null if value is deleted)
.map(snapshots => (snapshots.length === 0 ? null : pickFrom(snapshots, fields)))
// emit values only if changed
.distinctUntilChanged(hasEqualFields(fields))
)
}

function pickFrom(objects: Object[], fields: string[]) {
Expand Down
12 changes: 9 additions & 3 deletions packages/@sanity/preview/src/observeForPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ export default function observeForPreview(
// todo: We need a way of knowing the type of the referenced value by looking at the reference record alone
return resolveRefType(value, type).switchMap(
refType =>
refType ? observeForPreview(value, refType, fields) : Observable.of({snapshot: null})
refType
? observeForPreview(value, refType, fields)
: Observable.of({
type: type,
snapshot: null
})
)
}

Expand All @@ -38,9 +43,10 @@ export default function observeForPreview(
? configFields.filter(fieldName => fields.includes(fieldName))
: configFields
const paths = targetFields.map(key => selection[key].split('.'))
return observePaths(value, paths).map(snapshot => ({
return observePaths(value, paths)
.map(snapshot => ({
type: type,
snapshot: prepareForPreview(snapshot, type, viewOptions)
snapshot: snapshot && prepareForPreview(snapshot, type, viewOptions)
}))
}
return Observable.of({
Expand Down
15 changes: 15 additions & 0 deletions packages/@sanity/preview/src/utils/hasEqualFields.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @flow
export default function hasEqualFields(fields: string[]) {
return (object: ?Object, otherObject: ?Object) => {
if (object === otherObject) {
return true
}
if (!object || !otherObject) {
return false
}
if (typeof object !== 'object' || typeof otherObject !== 'object') {
return false
}
return fields.every(field => object[field] === otherObject[field])
}
}
27 changes: 27 additions & 0 deletions packages/@sanity/preview/src/utils/isUniqueBy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// @flow
const id = value => value

type ItemSelector<T> = (element: T) => any

// Takes an array and checks if each item is unique,
// optionally by specifying an itemSelector function that will be called with
// each item, returning the value to compare for uniqueness
export default function isUniqueBy<T>(
array: Array<T>,
itemSelector: ItemSelector<T> = id
): boolean {
let prevItem
let currItem
for (let i = 0; i < array.length; i++) {
if (i === 0) {
prevItem = itemSelector(array[i])
continue
}
currItem = itemSelector(array[i])
if (prevItem !== currItem) {
return false
}
prevItem = currItem
}
return true
}

0 comments on commit fd65783

Please sign in to comment.