Skip to content

Commit

Permalink
fix: showing add doc toast once document is received in the subscript…
Browse files Browse the repository at this point in the history
…ion (#8618)
  • Loading branch information
jordanl17 authored Feb 24, 2025
1 parent a102118 commit 61f7b1c
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 81 deletions.
9 changes: 8 additions & 1 deletion packages/sanity/src/core/releases/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ const releasesLocaleStrings = {
/** Title for information card on a published release */
'publish-info.title': 'This release is published',

/** Placeholder title for a release with no title */
'release-placeholder.title': 'Untitled',

/** Description for the review changes button in release tool */
'review.description': 'Add documents to this release to review changes',
/** Text for when a document is edited */
Expand All @@ -243,7 +246,7 @@ const releasesLocaleStrings = {
/** Description of a reverted release */
'revert-release.description': 'Revert changes to document versions in "{{title}}".',

/** Title o unschedule release dialog */
/** Title of unschedule release dialog */
'schedule-button.tooltip': 'Are you sure you want to unschedule the release?',

/** Schedule release button tooltip when user has no permissions to schedule */
Expand Down Expand Up @@ -325,6 +328,10 @@ const releasesLocaleStrings = {
'toast.archive.success': "The '<strong>{{title}}</strong>' release was archived.",
/** Text for toast when release failed to archive */
'toast.archive.error': "Failed to archive '<strong>{{title}}</strong>': {{error}}",
/** Description for toast when new version of document is created in release */
'toast.create-version.success': '{{documentTitle}} added to release',
/** Description for toast when creating new version of document in release failed */
'toast.create-version.error': 'Failed to add document to release: {{error}}',
/** Description for toast when release deletion failed */
'toast.delete.error': "Failed to delete '<strong>{{title}}</strong>': {{error}}",
/** Description for toast when release is successfully deleted */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,76 +1,31 @@
import {useTelemetry} from '@sanity/telemetry/react'
import {type SanityDocumentLike} from '@sanity/types'
import {LayerProvider, PortalProvider, useToast} from '@sanity/ui'
import {useCallback} from 'react'
import {type SanityDocument} from '@sanity/client'
import {LayerProvider, PortalProvider} from '@sanity/ui'

import {SearchPopover} from '../../../studio/components/navbar/search/components/SearchPopover'
import {SearchProvider} from '../../../studio/components/navbar/search/contexts/search/SearchProvider'
import {getDocumentVariantType} from '../../../util/getDocumentVariantType'
import {AddedVersion} from '../../__telemetry__/releases.telemetry'
import {useReleaseOperations} from '../../store/useReleaseOperations'
import {getReleaseIdFromReleaseDocumentId} from '../../util/getReleaseIdFromReleaseDocumentId'
import {useBundleDocuments} from './useBundleDocuments'

export type AddedDocument = Pick<SanityDocument, '_id' | '_type' | 'title'> &
Partial<SanityDocument>

export function AddDocumentSearch({
open,
onClose,
releaseId,
}: {
open: boolean
onClose: () => void
onClose: (document?: AddedDocument) => void
releaseId: string
}): React.JSX.Element {
const {createVersion} = useReleaseOperations()
const toast = useToast()
const telemetry = useTelemetry()

const {results} = useBundleDocuments(getReleaseIdFromReleaseDocumentId(releaseId))
const {results} = useBundleDocuments(releaseId)
const idsInRelease: string[] = results.map((doc) => doc.document._id)

const addDocument = useCallback(
async (item: Pick<SanityDocumentLike, '_id' | '_type' | 'title'>) => {
try {
await createVersion(getReleaseIdFromReleaseDocumentId(releaseId), item._id)

toast.push({
closable: true,
status: 'success',
title: `${item.title} added to release`,
})

const origin = getDocumentVariantType(item._id)

telemetry.log(AddedVersion, {
documentOrigin: origin,
})
} catch (error) {
/* empty */

toast.push({
closable: true,
status: 'error',
title: error.message,
})
}
},
[createVersion, releaseId, telemetry, toast],
)

const handleClose = useCallback(() => {
onClose()
}, [onClose])

return (
<LayerProvider zOffset={1}>
{/* eslint-disable-next-line @sanity/i18n/no-attribute-string-literals*/}
<SearchProvider perspective={['raw']} disabledDocumentIds={idsInRelease} canDisableAction>
<PortalProvider>
<SearchPopover
onClose={handleClose}
onItemSelect={addDocument}
open={open}
disableIntentLink
/>
<SearchPopover onClose={onClose} onItemSelect={onClose} open={open} disableIntentLink />
</PortalProvider>
</SearchProvider>
</LayerProvider>
Expand Down
141 changes: 122 additions & 19 deletions packages/sanity/src/core/releases/tool/detail/ReleaseSummary.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import {type SanityDocument} from '@sanity/client'
import {AddIcon} from '@sanity/icons'
import {Card, Container} from '@sanity/ui'
import {type RefObject, useCallback, useMemo, useState} from 'react'
import {useTelemetry} from '@sanity/telemetry/react'
import {Card, Container, useToast} from '@sanity/ui'
import {type RefObject, useCallback, useEffect, useMemo, useState} from 'react'

import {Button} from '../../../../ui-components'
import {useTranslation} from '../../../i18n'
import {getVersionId} from '../../../util/draftUtils'
import {getDocumentVariantType} from '../../../util/getDocumentVariantType'
import {AddedVersion} from '../../__telemetry__/releases.telemetry'
import {releasesLocaleNamespace} from '../../i18n'
import {type ReleaseDocument} from '../../store/types'
import {useReleaseOperations} from '../../store/useReleaseOperations'
import {getReleaseIdFromReleaseDocumentId} from '../../util/getReleaseIdFromReleaseDocumentId'
import {Table} from '../components/Table/Table'
import {AddDocumentSearch} from './AddDocumentSearch'
import {AddDocumentSearch, type AddedDocument} from './AddDocumentSearch'
import {DocumentActions} from './documentTable/DocumentActions'
import {getDocumentTableColumnDefs} from './documentTable/DocumentTableColumnDefs'
import {type DocumentHistory} from './documentTable/useReleaseHistory'
Expand All @@ -27,12 +34,30 @@ export interface ReleaseSummaryProps {
release: ReleaseDocument
}

const isBundleDocumentRow = (
maybeBundleDocumentRow: unknown,
): maybeBundleDocumentRow is BundleDocumentRow =>
!!maybeBundleDocumentRow &&
typeof maybeBundleDocumentRow === 'object' &&
'memoKey' in maybeBundleDocumentRow &&
'document' in maybeBundleDocumentRow &&
'validation' in maybeBundleDocumentRow &&
'previewValues' in maybeBundleDocumentRow &&
'history' in maybeBundleDocumentRow

export function ReleaseSummary(props: ReleaseSummaryProps) {
const {documents, documentsHistory, release, scrollContainerRef} = props
const toast = useToast()
const {createVersion} = useReleaseOperations()
const telemetry = useTelemetry()

const [openAddDocumentDialog, setAddDocumentDialog] = useState(false)
const [pendingAddedDocument, setPendingAddedDocument] = useState<BundleDocumentRow[]>([])

const {t} = useTranslation(releasesLocaleNamespace)

const releaseId = getReleaseIdFromReleaseDocumentId(release._id)

const aggregatedData = useMemo(
() =>
documents.map((document) => ({
Expand All @@ -45,14 +70,10 @@ export function ReleaseSummary(props: ReleaseSummaryProps) {
const renderRowActions = useCallback(
(rowProps: {datum: BundleDocumentRow | unknown}) => {
if (release.state !== 'active') return null
if (!isBundleDocumentRow(rowProps.datum)) return null
if (rowProps.datum.isPending) return null

return (
<DocumentActions
// TODO: Validate this with a proper asserter
document={rowProps.datum as BundleDocumentRow}
releaseTitle={release.metadata.title}
/>
)
return <DocumentActions document={rowProps.datum} releaseTitle={release.metadata.title} />
},
[release.metadata.title, release.state],
)
Expand All @@ -64,22 +85,104 @@ export function ReleaseSummary(props: ReleaseSummaryProps) {

const filterRows = useCallback(
(data: DocumentWithHistory[], searchTerm: string) =>
data.filter(({previewValues}) => {
data.filter(({previewValues, isPending}) => {
const title =
typeof previewValues.values.title === 'string' ? previewValues.values.title : 'Untitled'
return title.toLowerCase().includes(searchTerm.toLowerCase())
typeof previewValues.values.title === 'string'
? previewValues.values.title
: t('release-placeholder.title')

// always show the pending rows to visualise that documents are being added
return isPending || title.toLowerCase().includes(searchTerm.toLowerCase())
}),
[],
[t],
)

const closeAddDialog = useCallback(() => {
setAddDocumentDialog(false)
}, [])
const closeAddDialog = useCallback(
async (documentToAdd?: AddedDocument) => {
setAddDocumentDialog(false)
if (!documentToAdd) return

const versionDocumentId = getVersionId(documentToAdd._id, releaseId)
const pendingAddedDocumentId = `${versionDocumentId}-pending`

const pendingDocumentRow: DocumentWithHistory = {
memoKey: versionDocumentId,
previewValues: {isLoading: true, values: {}},
validation: {
isValidating: false,
validation: [],
hasError: false,
},
history: undefined,
document: {
...(documentToAdd as SanityDocument),
_id: pendingAddedDocumentId,
publishedDocumentExists: false,
},
isPending: true,
}

setPendingAddedDocument((prev) => [...prev, pendingDocumentRow])

try {
await createVersion(releaseId, documentToAdd._id)

const origin = getDocumentVariantType(documentToAdd._id)

telemetry.log(AddedVersion, {
documentOrigin: origin,
})
} catch (error) {
setPendingAddedDocument((prev) =>
prev.filter(({document}) => document._id !== pendingAddedDocumentId),
)

toast.push({
id: `add-version-to-release-${versionDocumentId}`,
closable: true,
status: 'error',
title: t('toast.create-version.error', {error: error.message}),
})
}
},
[createVersion, releaseId, t, telemetry, toast],
)

useEffect(() => {
const documentsNoLongerPending: string[] = []

pendingAddedDocument?.forEach((pendingDocument) => {
// once pending added document has been received by bundle store
if (
documents.find(({document}) => `${document._id}-pending` === pendingDocument.document._id)
) {
toast.push({
id: `add-version-to-release-${pendingDocument.document._id}`,
closable: true,
status: 'success',
title: t('toast.create-version.success', {documentTitle: pendingDocument.document.title}),
})
documentsNoLongerPending.push(pendingDocument.document._id)
}
})

if (documentsNoLongerPending.length)
// cleanup all resolved added documents
setPendingAddedDocument((prev) =>
prev.filter(({document}) => !documentsNoLongerPending.includes(document._id)),
)
}, [documents, pendingAddedDocument, t, toast])

const tableData = useMemo(
() =>
pendingAddedDocument.length ? [...aggregatedData, ...pendingAddedDocument] : aggregatedData,
[pendingAddedDocument, aggregatedData],
)

return (
<Card borderTop data-testid="document-table-card" ref={scrollContainerRef}>
<Table<DocumentWithHistory>
data={aggregatedData}
data={tableData}
emptyState={t('summary.no-documents')}
// eslint-disable-next-line @sanity/i18n/no-attribute-string-literals
rowId="document._id"
Expand All @@ -104,7 +207,7 @@ export function ReleaseSummary(props: ReleaseSummaryProps) {
<AddDocumentSearch
open={openAddDocumentDialog}
onClose={closeAddDialog}
releaseId={release._id}
releaseId={releaseId}
/>
</Card>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {Tooltip} from '../../../../../ui-components/tooltip'
import {UserAvatar} from '../../../../components'
import {RelativeTime} from '../../../../components/RelativeTime'
import {useSchema} from '../../../../hooks'
import {SanityDefaultPreview} from '../../../../preview/components/SanityDefaultPreview'
import {type ReleaseState} from '../../../store'
import {isGoingToUnpublish} from '../../../util/isGoingToUnpublish'
import {ReleaseDocumentPreview} from '../../components/ReleaseDocumentPreview'
Expand Down Expand Up @@ -63,8 +64,10 @@ const documentActionColumn: (t: TFunction<'releases', undefined>) => Column<Bund
</Flex>
),
cell: ({cellProps, datum}) => {
const willBeUnpublished = isGoingToUnpublish(datum.document)
const actionBadge = () => {
if (datum.isPending) return null

const willBeUnpublished = isGoingToUnpublish(datum.document)
if (willBeUnpublished) {
return (
<Badge radius={2} tone={'critical'} data-testid={`unpublish-badge-${datum.document._id}`}>
Expand Down Expand Up @@ -127,19 +130,25 @@ export const getDocumentTableColumnDefs: (
width: null,
style: {minWidth: '50%', maxWidth: '50%'},
sortTransform(value) {
if (!value.previewValues) return 0

return value.previewValues.values.title?.toLowerCase() || 0
},
header: (props) => (
<Headers.TableHeaderSearch {...props} placeholder={t('search-documents-placeholder')} />
),
cell: ({cellProps, datum}) => (
<Box {...cellProps} flex={1} padding={1} paddingRight={2} sizing="border">
<MemoReleaseDocumentPreview
item={datum}
releaseId={releaseId}
releaseState={releaseState}
documentRevision={datum.document._rev}
/>
{datum.isPending ? (
<SanityDefaultPreview isPlaceholder />
) : (
<MemoReleaseDocumentPreview
item={datum}
releaseId={releaseId}
releaseState={releaseState}
documentRevision={datum.document._rev}
/>
)}
</Box>
),
},
Expand All @@ -153,7 +162,14 @@ export const getDocumentTableColumnDefs: (
</Flex>
),
cell: ({cellProps, datum: {document, history}}) => (
<Flex {...cellProps} align="center" paddingX={2} paddingY={3} sizing="border">
<Flex
{...cellProps}
align="center"
paddingX={2}
paddingY={3}
style={{minWidth: 130}}
sizing="border"
>
{document._updatedAt && (
<Flex align="center" gap={2}>
{history?.lastEditedBy && <UserAvatar size={0} user={history.lastEditedBy} />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export interface DocumentValidationStatus extends ValidationStatus {

export interface DocumentInRelease {
memoKey: string
isPending?: boolean
document: SanityDocument & {publishedDocumentExists: boolean}
validation: DocumentValidationStatus
previewValues: {isLoading: boolean; values: ReturnType<typeof prepareForPreview>}
Expand Down

0 comments on commit 61f7b1c

Please sign in to comment.