Skip to content

Commit

Permalink
feat(releases): published label; unarchive server action; and fixture…
Browse files Browse the repository at this point in the history
… fix (#8140)
  • Loading branch information
jordanl17 authored Dec 25, 2024
1 parent 91ac9b0 commit 6c1a71e
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,9 @@ export const publishedASAPRelease: ReleaseDocument = {
_createdAt: '2023-10-10T08:00:00Z',
_updatedAt: '2023-10-10T09:00:00Z',
state: 'published',
publishAt: '2023-10-10T09:00:00Z',
metadata: {
title: 'published Release',
releaseType: 'asap',
intendedPublishAt: '2023-10-10T09:00:00Z',
description: 'archived Release description',
},
}
Expand Down
16 changes: 11 additions & 5 deletions packages/sanity/src/core/releases/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ const releasesLocaleStrings = {
'action.immediate-revert-release': 'Revert now',
/** Label for unarchiving a release */
'action.unarchive': 'Unarchive release',
/** Header for the dialog confirming the archive of a release */
'archive-dialog.confirm-archive-header':
"Are you sure you want to archive the '{{title}}' release?",

/* The text for the activity event when a document is added to a release */
'activity.event.add-document': 'added a document version',
/* The text for the activity event when the release is archived */
Expand Down Expand Up @@ -75,6 +71,9 @@ const releasesLocaleStrings = {
/** The title for the activity panel shown in the releases detail screen */
'activity.panel.title': 'Activity',

/** Header for the dialog confirming the archive of a release */
'archive-dialog.confirm-archive-header':
"Are you sure you want to archive the '{{title}}' release?",
/** Title for the dialog confirming the archive of a release */
'archive-dialog.confirm-archive-title':
"Are you sure you want to archive the <strong>'{{title}}'</strong> release?",
Expand All @@ -91,7 +90,9 @@ const releasesLocaleStrings = {
/** Text for when a release / document was created */
'created': 'Created <RelativeTime/>',

/** Text for the releases detail screen when a release was published */
/** Text for the releases detail screen when a release was published ASAP */
'dashboard.details.published-asap': 'Published',
/** Text for the releases detail screen when a release was published from scheduling */
'dashboard.details.published-on': 'Published on {{date}}',

/** Text for the releases detail screen in the pin release button. */
Expand Down Expand Up @@ -304,6 +305,11 @@ const releasesLocaleStrings = {
'toast.unschedule.error': "Failed to unscheduled '<strong>{{title}}</strong>': {{error}}",
/** Text for toast when release has been unschedule */
'toast.unschedule.success': "The '<strong>{{title}}</strong>' release was unscheduled.",
/** Text for toast when release has been unarchived */
'toast.unarchive.success': "The '<strong>{{title}}</strong>' release was unarchived.",
/** Text for toast when release failed to unarchive */
'toast.unarchive.error': "Failed to unarchive '<strong>{{title}}</strong>': {{error}}",
/** Description for toast when release deletion failed */
/** Text for tooltip when a release has been scheduled */
'type-picker.tooltip.scheduled': 'The release is scheduled, unschedule it to change type',
/** Text for toast when release failed to revert */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import {
} from '@sanity/icons'
import {type DefinedTelemetryLog, useTelemetry} from '@sanity/telemetry/react'
import {Menu, Spinner, Text, useToast} from '@sanity/ui'
import {useCallback, useMemo, useState} from 'react'
import {type MouseEventHandler, useCallback, useMemo, useState} from 'react'
import {useRouter} from 'sanity/router'

import {Button, Dialog, MenuButton, MenuItem} from '../../../../../ui-components'
import {Translate, useTranslation} from '../../../../i18n'
import {
ArchivedRelease,
DeletedRelease,
UnarchivedRelease,
UnscheduledRelease,
} from '../../../__telemetry__/releases.telemetry'
import {releasesLocaleNamespace} from '../../../i18n'
Expand All @@ -32,7 +33,7 @@ export type ReleaseMenuButtonProps = {
release: ReleaseDocument
}

type ReleaseAction = 'archive' | 'delete' | 'unschedule'
type ReleaseAction = 'archive' | 'unarchive' | 'delete' | 'unschedule'

interface BaseReleaseActionsMap {
toastSuccessI18nKey: string
Expand Down Expand Up @@ -78,6 +79,12 @@ const RELEASE_ACTION_MAP: Record<
toastFailureI18nKey: 'toast.archive.error',
telemetry: ArchivedRelease,
},
unarchive: {
confirmDialog: false,
toastSuccessI18nKey: 'toast.unarchive.success',
toastFailureI18nKey: 'toast.unarchive.error',
telemetry: UnarchivedRelease,
},
unschedule: {
confirmDialog: false,
toastSuccessI18nKey: 'toast.unschedule.success',
Expand All @@ -89,7 +96,7 @@ const RELEASE_ACTION_MAP: Record<
export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps) => {
const toast = useToast()
const router = useRouter()
const {archive, deleteRelease, unschedule} = useReleaseOperations()
const {archive, unarchive, deleteRelease, unschedule} = useReleaseOperations()
const {loading: isLoadingReleaseDocuments, results: releaseDocuments} = useBundleDocuments(
getReleaseIdFromReleaseDocumentId(release._id),
)
Expand All @@ -116,6 +123,7 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)
const actionLookup = {
delete: handleDelete,
archive,
unarchive,
unschedule,
}
const actionValues = RELEASE_ACTION_MAP[action]
Expand Down Expand Up @@ -160,6 +168,7 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)
releaseMenuDisabled,
handleDelete,
archive,
unarchive,
unschedule,
release._id,
telemetry,
Expand All @@ -169,11 +178,6 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)
],
)

const handleUnarchive = async () => {
// noop
// TODO: similar to handleArchive - complete once server action exists
}

const confirmActionDialog = useMemo(() => {
if (!selectedAction) return null

Expand Down Expand Up @@ -224,8 +228,10 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)
t,
])

const handleOnInitiateAction = useCallback(
(action: ReleaseAction) => {
const handleOnInitiateAction = useCallback<MouseEventHandler<HTMLDivElement>>(
(event) => {
const action = event.currentTarget.getAttribute('data-value') as ReleaseAction

if (releaseDocuments.length > 0 && RELEASE_ACTION_MAP[action].confirmDialog) {
setSelectedAction(action)
} else {
Expand All @@ -241,9 +247,8 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)
if (release.state === 'archived')
return (
<MenuItem
onClick={handleUnarchive}
// TODO: disabled as CL action not yet impl
disabled
data-value="unarchive"
onClick={handleOnInitiateAction}
icon={UnarchiveIcon}
text={t('action.unarchive')}
data-testid="unarchive-release-menu-item"
Expand All @@ -256,7 +261,8 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)
disabled: !['scheduled', 'scheduling'].includes(release.state) || isPerformingOperation,
content: t('action.archive.tooltip'),
}}
onClick={() => handleOnInitiateAction('archive')}
data-value="archive"
onClick={handleOnInitiateAction}
icon={ArchiveIcon}
text={t('action.archive')}
data-testid="archive-release-menu-item"
Expand All @@ -270,7 +276,8 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)

return (
<MenuItem
onClick={() => handleOnInitiateAction('delete')}
data-value="delete"
onClick={handleOnInitiateAction}
disabled={releaseMenuDisabled || isPerformingOperation}
icon={TrashIcon}
text={t('action.delete-release')}
Expand All @@ -284,7 +291,8 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps)

return (
<MenuItem
onClick={() => handleOnInitiateAction('unschedule')}
data-value="unschedule"
onClick={handleOnInitiateAction}
disabled={releaseMenuDisabled || isPerformingOperation}
icon={CloseCircleIcon}
text={t('action.unschedule')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ 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 {ToneIcon} from '../../../../../ui-components/toneIcon/ToneIcon'
Expand All @@ -27,7 +26,6 @@ export const ReleasePublishAllButton = ({
disabled,
}: ReleasePublishAllButtonProps) => {
const toast = useToast()
const router = useRouter()
const {publishRelease} = useReleaseOperations()
const {t} = useTranslation(releasesLocaleNamespace)
const perspective = usePerspective()
Expand Down Expand Up @@ -69,8 +67,6 @@ export const ReleasePublishAllButton = ({
</Text>
),
})
// TODO: handle a published release on the document list
router.navigate({})
if (
isReleaseDocument(perspective.selectedPerspective) &&
perspective.selectedPerspective?._id === release._id
Expand All @@ -94,7 +90,7 @@ export const ReleasePublishAllButton = ({
} finally {
setPublishBundleStatus('idle')
}
}, [release, publishBundleStatus, publishRelease, telemetry, toast, t, router, perspective])
}, [release, publishBundleStatus, publishRelease, telemetry, toast, t, perspective])

const confirmPublishDialog = useMemo(() => {
if (publishBundleStatus === 'idle') return null
Expand Down
121 changes: 76 additions & 45 deletions packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {LockIcon} from '@sanity/icons'
import {Flex, Spinner, Stack, TabList, Text, useClickOutsideEvent} from '@sanity/ui'
import {Card, Flex, Spinner, Stack, TabList, Text, useClickOutsideEvent} from '@sanity/ui'
import {format, isBefore, isValid} from 'date-fns'
import {isEqual} from 'lodash'
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
Expand Down Expand Up @@ -94,18 +94,31 @@ export function ReleaseTypePicker(props: {release: ReleaseDocument}): JSX.Elemen
const isReleaseScheduled = isReleaseScheduledOrScheduling(release)

const publishDateLabel = useMemo(() => {
if (release.state === 'published') {
if (isPublishDateInPast && release.publishAt)
return tRelease('dashboard.details.published-on', {
date: format(new Date(publishDate), 'MMM d, yyyy, pp'),
})

return tRelease('dashboard.details.published-asap')
}

if (releaseType === 'asap') return t('release.type.asap')
if (releaseType === 'undecided') return t('release.type.undecided')
const labelDate = publishDate || inputValue
if (!labelDate) return null

if (isPublishDateInPast && release.publishAt)
return tRelease('dashboard.details.published-on', {
date: format(new Date(publishDate), 'MMM d, yyyy'),
})

return format(new Date(labelDate), `PPpp`)
}, [inputValue, isPublishDateInPast, publishDate, release.publishAt, releaseType, t, tRelease])
}, [
inputValue,
isPublishDateInPast,
publishDate,
release.publishAt,
release.state,
releaseType,
t,
tRelease,
])

const handleButtonReleaseTypeChange = useCallback((pickedReleaseType: ReleaseType) => {
setDateInputOpen(pickedReleaseType === 'scheduled')
Expand Down Expand Up @@ -189,6 +202,34 @@ export function ReleaseTypePicker(props: {release: ReleaseDocument}): JSX.Elemen
)
}

const tone =
release.state === 'published'
? 'positive'
: getReleaseTone({...release, metadata: {...release.metadata, releaseType}})

const labelContent = useMemo(
() => (
<Flex flex={1} gap={2}>
{isUpdating ? (
<Spinner size={1} data-testid="updating-release-spinner" />
) : (
<ReleaseAvatar tone={tone} padding={0} />
)}

<Text muted size={1} data-testid="release-type-label" weight="medium">
{publishDateLabel}
</Text>

{isReleaseScheduled && (
<Text size={1}>
<LockIcon />
</Text>
)}
</Flex>
),
[isReleaseScheduled, isUpdating, publishDateLabel, tone],
)

return (
<Popover
content={<PopoverContent />}
Expand All @@ -197,44 +238,34 @@ export function ReleaseTypePicker(props: {release: ReleaseDocument}): JSX.Elemen
placement="bottom-start"
ref={popoverRef}
>
<Button
disabled={
isReleaseScheduled || release.state === 'archived' || release.state === 'published'
}
mode="bleed"
onClick={handleOnPickerClick}
padding={2}
ref={buttonRef}
tooltipProps={{
placement: 'bottom',
content: isReleaseScheduled && tRelease('type-picker.tooltip.scheduled'),
}}
selected={open}
tone={getReleaseTone({...release, metadata: {...release.metadata, releaseType}})}
style={{borderRadius: '999px'}}
data-testid="release-type-picker"
>
<Flex flex={1} gap={2}>
{isUpdating ? (
<Spinner size={1} data-testid="updating-release-spinner" />
) : (
<ReleaseAvatar
tone={getReleaseTone({...release, metadata: {...release.metadata, releaseType}})}
padding={0}
/>
)}

<Text muted size={1} weight="medium">
{publishDateLabel}
</Text>

{isReleaseScheduled && (
<Text size={1}>
<LockIcon />
</Text>
)}
</Flex>
</Button>
{release.state === 'published' ? (
<Card
tone="positive"
data-testid="published-release-type-label"
padding={2}
style={{borderRadius: '999px'}}
>
{labelContent}
</Card>
) : (
<Button
disabled={isReleaseScheduled || release.state === 'archived'}
mode="bleed"
onClick={handleOnPickerClick}
padding={2}
ref={buttonRef}
tooltipProps={{
placement: 'bottom',
content: isReleaseScheduled && tRelease('type-picker.tooltip.scheduled'),
}}
selected={open}
tone={tone}
style={{borderRadius: '999px'}}
data-testid="release-type-picker"
>
{labelContent}
</Button>
)}
</Popover>
)
}
Loading

0 comments on commit 6c1a71e

Please # to comment.