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(releases): published label; unarchive server action; and fixture fix #8140

Merged
merged 4 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading