Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: publish action used for immediate release publish #7695

Merged
merged 11 commits into from
Oct 30, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,18 @@ export function ReleaseDetailsDialog(props: ReleaseDetailsDialogProps): JSX.Elem
const dialogTitle =
formAction === 'edit' ? t('release.dialog.edit.title') : t('release.dialog.create.title')

const isReleaseScheduled =
release && (release.state === 'scheduled' || release.state === 'scheduling')

return (
<Dialog header={dialogTitle} id="create-release-dialog" onClose={onCancel} width={1}>
<form onSubmit={handleOnSubmit}>
<Box padding={4}>
<ReleaseForm onChange={handleOnChange} value={value} />
<ReleaseForm
onChange={handleOnChange}
value={value}
isReleaseScheduled={isReleaseScheduled}
/>
</Box>
<Flex justify="flex-end" paddingTop={5}>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ const DEFAULT_METADATA: ReleaseDocument['metadata'] = {
export function ReleaseForm(props: {
onChange: (params: EditableReleaseDocument) => void
value: EditableReleaseDocument
isReleaseScheduled?: boolean
}): JSX.Element {
const {onChange, value} = props
const {onChange, value, isReleaseScheduled = false} = props
const {title, description, releaseType} = value.metadata || {}
const publishAt = value.publishAt
// derive the action from whether the initial value prop has a slug
Expand Down Expand Up @@ -81,7 +82,10 @@ export function ReleaseForm(props: {
const handleButtonReleaseTypeChange = useCallback(
(pickedReleaseType: ReleaseType) => {
setButtonReleaseType(pickedReleaseType)
onChange({...value, metadata: {...value.metadata, releaseType: pickedReleaseType}})
onChange({
...value,
metadata: {...value.metadata, releaseType: pickedReleaseType, intendedPublishAt: undefined},
})
},
[onChange, value],
)
Expand All @@ -91,18 +95,21 @@ export function ReleaseForm(props: {
<Stack space={2} style={{margin: -1}}>
<Flex gap={1}>
<Button
disabled={isReleaseScheduled}
mode="bleed"
onClick={() => handleButtonReleaseTypeChange('asap')}
selected={buttonReleaseType === 'asap'}
text={t('release.type.asap')}
/>
<Button
disabled={isReleaseScheduled}
mode="bleed"
onClick={() => handleButtonReleaseTypeChange('scheduled')}
selected={buttonReleaseType === 'scheduled'}
text={t('release.type.scheduled')}
/>
<Button
disabled={isReleaseScheduled}
mode="bleed"
onClick={() => handleButtonReleaseTypeChange('undecided')}
selected={buttonReleaseType === 'undecided'}
Expand All @@ -113,6 +120,7 @@ export function ReleaseForm(props: {
{buttonReleaseType === 'scheduled' && (
<DateTimeInput
selectTime
readOnly={isReleaseScheduled}
monthPickerVariant={MONTH_PICKER_VARIANT.carousel}
onChange={handleBundlePublishAtChange}
calendarLabels={calendarLabels}
Expand Down
12 changes: 5 additions & 7 deletions packages/sanity/src/core/releases/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const releasesLocaleStrings = {
/** Action text for adding a document to release */
'action.add-document': 'Add document',
/** Action text for archiving a release */
'action.archive': 'Archive',
'action.archive': 'Archive release',
/** Action text for showing the archived releases */
'action.archived': 'Archived',
/** Action text for deleting a release */
Expand All @@ -17,23 +17,21 @@ const releasesLocaleStrings = {
/** Description for toast when release is successfully deleted */
'action.delete.success': '<strong>{{title}}</strong> release was successfully deleted',
/** Action text for editing a release */
'action.edit': 'Edit',
'action.edit': 'Edit release',
/** Action text for opening a release */
'action.open': 'Open',
/** Action text for publishing a release */
'action.publish': 'Publish',
/** Action text for scheduling a release */
'action.schedule': 'Schedule for publishing',
'action.schedule': 'Schedule for publishing...',
/** Action text for scheduling a release */
'action.unschedule': 'Unschedule',
/** Action text for publishing all documents in a release (and the release itself) */
'action.publish-all': 'Publish all',
'action.publish-all-documents': 'Publish all documents',
/** Text for the review changes button in release tool */
'action.review': 'Review changes',
/** Text for the summary button in release tool */
'actions.summary': 'Summary',
/** Label for unarchiving a release */
'action.unarchive': 'Unarchive',
'action.unarchive': 'Unarchive release',

/** Title for changes to published documents */
'changes-published-docs.title': 'Changes to published documents',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
import {useTelemetry} from '@sanity/telemetry/react'
import {Box, Flex, Menu, Spinner, Text} from '@sanity/ui'
import {type FormEventHandler, useState} from 'react'
import {LoadingBlock} from 'sanity'

import {Button, Dialog, MenuButton, MenuItem} from '../../../../../ui-components'
import {LoadingBlock} from '../../../../components/loadingBlock'
import {useTranslation} from '../../../../i18n'
import {type ReleaseDocument} from '../../../../store/release/types'
import {useReleaseOperations} from '../../../../store/release/useReleaseOperations'
Expand All @@ -23,6 +23,8 @@ export type ReleaseMenuButtonProps = {
release?: ReleaseDocument
}

const ARCHIVIABLE_STATES = ['active', 'published']

export const ReleaseMenuButton = ({disabled, release}: ReleaseMenuButtonProps) => {
const {archive} = useReleaseOperations()
const isReleaseArchived = release?.state === 'archived'
Expand Down Expand Up @@ -74,9 +76,9 @@ export const ReleaseMenuButton = ({disabled, release}: ReleaseMenuButtonProps) =
text={t('action.edit')}
data-testid="edit-release"
/>
{/* <MenuItem onClick={() => setSelectedAction('')} /> */}
<MenuItem
onClick={() => setSelectedAction('confirm-archive')}
disabled={!release || !ARCHIVIABLE_STATES.includes(release.state)}
icon={isReleaseArchived ? UnarchiveIcon : ArchiveIcon}
text={isReleaseArchived ? t('action.unarchive') : t('action.archive')}
data-testid="archive-release"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {ErrorOutlineIcon, PublishIcon} from '@sanity/icons'
import {useTelemetry} from '@sanity/telemetry/react'
import {Flex, Text, useToast} from '@sanity/ui'
import {useCallback, useMemo, useState} from 'react'
import {useRouter} from 'sanity/router'

import {Button, Dialog} from '../../../../../ui-components'
import {Translate, useTranslation} from '../../../../i18n'
Expand All @@ -10,7 +11,6 @@ import {useReleaseOperations} from '../../../../store/release/useReleaseOperatio
import {PublishedRelease} from '../../../__telemetry__/releases.telemetry'
import {releasesLocaleNamespace} from '../../../i18n'
import {type DocumentInRelease} from '../../../tool/detail/useBundleDocuments'
import {useObserveDocumentRevisions} from './useObserveDocumentRevisions'

interface ReleasePublishAllButtonProps {
release: ReleaseDocument
Expand All @@ -24,56 +24,59 @@ export const ReleasePublishAllButton = ({
disabled,
}: ReleasePublishAllButtonProps) => {
const toast = useToast()
const router = useRouter()
const {publishRelease} = useReleaseOperations()
const {t} = useTranslation(releasesLocaleNamespace)
const telemetry = useTelemetry()
const [publishBundleStatus, setPublishBundleStatus] = useState<'idle' | 'confirm' | 'publishing'>(
'idle',
)

const publishedDocumentsRevisions = useObserveDocumentRevisions(
documents.map(({document}) => document),
)

const isValidatingDocuments = documents.some(({validation}) => validation.isValidating)
const hasDocumentValidationErrors = documents.some(({validation}) => validation.hasError)

const isPublishButtonDisabled = disabled || isValidatingDocuments || hasDocumentValidationErrors

const handleConfirmPublishAll = useCallback(async () => {
if (!release || !publishedDocumentsRevisions) return
if (!release) return

try {
setPublishBundleStatus('publishing')
await publishRelease(
release._id,
documents.map(({document}) => document),
publishedDocumentsRevisions,
)
await publishRelease(release._id)
telemetry.log(PublishedRelease)
toast.push({
closable: true,
status: 'success',
title: (
<Text muted size={1}>
<Translate t={t} i18nKey="toast.published" values={{title: release.metadata.title}} />
<Translate
t={t}
i18nKey="toast.publish.success"
values={{title: release.metadata.title}}
/>
</Text>
),
})
// TODO: handle a published release on the document list
router.navigate({})
} catch (publishingError) {
toast.push({
status: 'error',
title: (
<Text muted size={1}>
<Translate t={t} i18nKey="toast.error" values={{title: release.metadata.title}} />
<Translate
t={t}
i18nKey="toast.publish.error"
values={{title: release.metadata.title}}
/>
</Text>
),
})
console.error(publishingError)
} finally {
setPublishBundleStatus('idle')
}
}, [release, documents, publishRelease, publishedDocumentsRevisions, t, telemetry, toast])
}, [release, publishRelease, telemetry, toast, t, router])

const confirmPublishDialog = useMemo(() => {
if (publishBundleStatus === 'idle') return null
Expand All @@ -85,8 +88,8 @@ export const ReleasePublishAllButton = ({
onClose={() => setPublishBundleStatus('idle')}
footer={{
confirmButton: {
text: t('action.publish'),
tone: 'default',
text: t('action.publish-all-documents'),
tone: 'positive',
onClick: handleConfirmPublishAll,
loading: publishBundleStatus === 'publishing',
disabled: publishBundleStatus === 'publishing',
Expand All @@ -108,7 +111,7 @@ export const ReleasePublishAllButton = ({
</Text>
</Dialog>
)
}, [release.metadata.title, documents.length, handleConfirmPublishAll, publishBundleStatus, t])
}, [publishBundleStatus, t, handleConfirmPublishAll, release, documents.length])

const publishTooltipContent = useMemo(() => {
if (!hasDocumentValidationErrors && !isValidatingDocuments) return null
Expand Down Expand Up @@ -145,10 +148,11 @@ export const ReleasePublishAllButton = ({
}}
icon={PublishIcon}
disabled={isPublishButtonDisabled || publishBundleStatus === 'publishing'}
text={t('action.publish-all')}
text={t('action.publish-all-documents')}
onClick={() => setPublishBundleStatus('confirm')}
loading={publishBundleStatus === 'publishing'}
data-testid="publish-all-button"
tone="positive"
/>
{confirmPublishDialog}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {CalendarIcon, ErrorOutlineIcon} from '@sanity/icons'
import {ClockIcon, ErrorOutlineIcon} from '@sanity/icons'
import {useTelemetry} from '@sanity/telemetry/react'
import {Flex, Stack, Text, useToast} from '@sanity/ui'
import {useCallback, useMemo, useState} from 'react'
Expand Down Expand Up @@ -150,41 +150,42 @@ export const ReleaseScheduleButton = ({
dateFormatter,
])

const scheduleTooltipContent = useMemo(() => {
const tooltipText = () => {
if (isValidatingDocuments) {
return t('schedule-button-tooltip.validation.loading')
}
const tooltipText = useMemo(() => {
if (isValidatingDocuments) {
return t('schedule-button-tooltip.validation.loading')
}

if (hasDocumentValidationErrors) {
return t('schedule-button-tooltip.validation.error')
}
if (hasDocumentValidationErrors) {
return t('schedule-button-tooltip.validation.error')
}

if (release.state === 'scheduled' || release.state === 'scheduling') {
return t('schedule-button-tooltip.already-scheduled')
}
return null
if (release.state === 'scheduled' || release.state === 'scheduling') {
return t('schedule-button-tooltip.already-scheduled')
}
return null
}, [hasDocumentValidationErrors, isValidatingDocuments, release.state, t])

const scheduleTooltipContent = useMemo(() => {
return (
<Flex gap={1} align="center">
<ErrorOutlineIcon />
<Text muted size={1}>
{tooltipText()}
{tooltipText}
</Text>
</Flex>
)
}, [hasDocumentValidationErrors, isValidatingDocuments, release.state, t])
}, [tooltipText])

return (
<>
<Button
tooltipProps={{
disabled: !isScheduleButtonDisabled,
disabled: !tooltipText,
content: scheduleTooltipContent,
placement: 'bottom',
}}
icon={CalendarIcon}
tone="primary"
icon={ClockIcon}
disabled={isScheduleButtonDisabled || status === 'scheduling'}
text={t('action.schedule')}
onClick={() => setStatus('confirm')}
Expand Down
Loading
Loading