From 5683c4243815c3a09993943a1ac2f3815a065047 Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Tue, 24 Dec 2024 23:44:18 +0000 Subject: [PATCH 1/4] feat: supporting server action for unarchive --- .../src/core/releases/i18n/resources.ts | 16 +++++--- .../ReleaseMenuButton/ReleaseMenuButton.tsx | 40 +++++++++++-------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/sanity/src/core/releases/i18n/resources.ts b/packages/sanity/src/core/releases/i18n/resources.ts index 40fa620f7aa..eafb48ec6af 100644 --- a/packages/sanity/src/core/releases/i18n/resources.ts +++ b/packages/sanity/src/core/releases/i18n/resources.ts @@ -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 */ @@ -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 '{{title}}' release?", @@ -91,7 +90,9 @@ const releasesLocaleStrings = { /** Text for when a release / document was created */ 'created': 'Created ', - /** 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. */ @@ -304,6 +305,11 @@ const releasesLocaleStrings = { 'toast.unschedule.error': "Failed to unscheduled '{{title}}': {{error}}", /** Text for toast when release has been unschedule */ 'toast.unschedule.success': "The '{{title}}' release was unscheduled.", + /** Text for toast when release has been unarchived */ + 'toast.unarchive.success': "The '{{title}}' release was unarchived.", + /** Text for toast when release failed to unarchive */ + 'toast.unarchive.error': "Failed to unarchive '{{title}}': {{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 */ diff --git a/packages/sanity/src/core/releases/tool/components/ReleaseMenuButton/ReleaseMenuButton.tsx b/packages/sanity/src/core/releases/tool/components/ReleaseMenuButton/ReleaseMenuButton.tsx index 13898f9162a..28c08ec93d6 100644 --- a/packages/sanity/src/core/releases/tool/components/ReleaseMenuButton/ReleaseMenuButton.tsx +++ b/packages/sanity/src/core/releases/tool/components/ReleaseMenuButton/ReleaseMenuButton.tsx @@ -7,7 +7,7 @@ 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' @@ -15,6 +15,7 @@ import {Translate, useTranslation} from '../../../../i18n' import { ArchivedRelease, DeletedRelease, + UnarchivedRelease, UnscheduledRelease, } from '../../../__telemetry__/releases.telemetry' import {releasesLocaleNamespace} from '../../../i18n' @@ -32,7 +33,7 @@ export type ReleaseMenuButtonProps = { release: ReleaseDocument } -type ReleaseAction = 'archive' | 'delete' | 'unschedule' +type ReleaseAction = 'archive' | 'unarchive' | 'delete' | 'unschedule' interface BaseReleaseActionsMap { toastSuccessI18nKey: string @@ -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', @@ -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), ) @@ -116,6 +123,7 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps) const actionLookup = { delete: handleDelete, archive, + unarchive, unschedule, } const actionValues = RELEASE_ACTION_MAP[action] @@ -160,6 +168,7 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps) releaseMenuDisabled, handleDelete, archive, + unarchive, unschedule, release._id, telemetry, @@ -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 @@ -224,8 +228,10 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps) t, ]) - const handleOnInitiateAction = useCallback( - (action: ReleaseAction) => { + const handleOnInitiateAction = useCallback>( + (event) => { + const action = event.currentTarget.getAttribute('data-value') as ReleaseAction + if (releaseDocuments.length > 0 && RELEASE_ACTION_MAP[action].confirmDialog) { setSelectedAction(action) } else { @@ -241,9 +247,8 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps) if (release.state === 'archived') return ( handleOnInitiateAction('archive')} + data-value="archive" + onClick={handleOnInitiateAction} icon={ArchiveIcon} text={t('action.archive')} data-testid="archive-release-menu-item" @@ -270,7 +276,8 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps) return ( handleOnInitiateAction('delete')} + data-value="delete" + onClick={handleOnInitiateAction} disabled={releaseMenuDisabled || isPerformingOperation} icon={TrashIcon} text={t('action.delete-release')} @@ -284,7 +291,8 @@ export const ReleaseMenuButton = ({ignoreCTA, release}: ReleaseMenuButtonProps) return ( handleOnInitiateAction('unschedule')} + data-value="unschedule" + onClick={handleOnInitiateAction} disabled={releaseMenuDisabled || isPerformingOperation} icon={CloseCircleIcon} text={t('action.unschedule')} From e2f4ade2edc04d040c491390e0d8ff22349eff69 Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Tue, 24 Dec 2024 23:44:45 +0000 Subject: [PATCH 2/4] feat: type picker shows published for asap and scheduled published releases --- .../releases/__fixtures__/release.fixture.ts | 2 - .../tool/detail/ReleaseTypePicker.tsx | 121 +++++++++++------- .../__tests__/ReleaseTypePicker.test.tsx | 28 +++- 3 files changed, 100 insertions(+), 51 deletions(-) diff --git a/packages/sanity/src/core/releases/__fixtures__/release.fixture.ts b/packages/sanity/src/core/releases/__fixtures__/release.fixture.ts index 90aafcc034b..74f64fd6559 100644 --- a/packages/sanity/src/core/releases/__fixtures__/release.fixture.ts +++ b/packages/sanity/src/core/releases/__fixtures__/release.fixture.ts @@ -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', }, } diff --git a/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx b/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx index 3bba587c407..8cccb34e5c7 100644 --- a/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx +++ b/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx @@ -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' @@ -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'), + }) + + 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') @@ -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( + () => ( + + {isUpdating ? ( + + ) : ( + + )} + + + {publishDateLabel} + + + {isReleaseScheduled && ( + + + + )} + + ), + [isReleaseScheduled, isUpdating, publishDateLabel, tone], + ) + return ( } @@ -197,44 +238,34 @@ export function ReleaseTypePicker(props: {release: ReleaseDocument}): JSX.Elemen placement="bottom-start" ref={popoverRef} > - + {release.state === 'published' ? ( + + {labelContent} + + ) : ( + + )} ) } diff --git a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx index 739c86487fa..ed67181c39e 100644 --- a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx +++ b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx @@ -9,6 +9,7 @@ import { activeScheduledRelease, activeUndecidedRelease, publishedASAPRelease, + scheduledRelease, } from '../../../__fixtures__/release.fixture' import {releasesUsEnglishLocaleBundle} from '../../../i18n' import { @@ -34,7 +35,7 @@ const renderComponent = async (release = activeASAPRelease) => { render(, {wrapper}) await waitFor(() => { - expect(screen.getByTestId('release-type-picker')).toBeInTheDocument() + expect(screen.getByTestId('release-type-label')).toBeInTheDocument() }) } @@ -67,6 +68,26 @@ describe('ReleaseTypePicker', () => { expect(screen.getByText('Oct 10, 2023', {exact: false})).toBeInTheDocument() }) + + it('renders the label with a published text when release was asap published', async () => { + await renderComponent(publishedASAPRelease) + + expect(screen.queryByRole('button')).not.toBeInTheDocument() + + expect(screen.getByTestId('published-release-type-label')).toBeInTheDocument() + + expect(screen.getByText('Published')).toBeInTheDocument() + }) + + it('renders the label with a published text when release was schedule published', async () => { + await renderComponent({...scheduledRelease, state: 'published'}) + + expect(screen.queryByRole('button')).not.toBeInTheDocument() + + expect(screen.getByTestId('published-release-type-label')).toBeInTheDocument() + + expect(screen.getByText('Published on Oct 10, 2023')).toBeInTheDocument() + }) }) describe('interacting with the popup content', () => { @@ -172,11 +193,10 @@ describe('ReleaseTypePicker', () => { expect(pickerButton).toBeDisabled() }) - it('disables the picker for published releases', async () => { + it('does not show button for picker when release is published state', async () => { await renderComponent(publishedASAPRelease) - const pickerButton = screen.getByRole('button') - expect(pickerButton).toBeDisabled() + expect(screen.queryByRole('button')).not.toBeInTheDocument() }) it('shows a spinner when updating the release', async () => { From b54af08f982e6683eae57254b79b94921f2bbaaa Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Tue, 24 Dec 2024 23:45:47 +0000 Subject: [PATCH 3/4] fix: publish all doesn't need to navigate to overview anymore --- .../releaseCTAButtons/ReleasePublishAllButton.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleasePublishAllButton.tsx b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleasePublishAllButton.tsx index 527073e75f5..3fd5bc2fd7b 100644 --- a/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleasePublishAllButton.tsx +++ b/packages/sanity/src/core/releases/tool/components/releaseCTAButtons/ReleasePublishAllButton.tsx @@ -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' @@ -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() @@ -69,8 +67,6 @@ export const ReleasePublishAllButton = ({ ), }) - // TODO: handle a published release on the document list - router.navigate({}) if ( isReleaseDocument(perspective.selectedPerspective) && perspective.selectedPerspective?._id === release._id @@ -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 From 08963055e983d9f6ae9e3af7b9ea3c3ed9179a7c Mon Sep 17 00:00:00 2001 From: Jordan Lawrence Date: Wed, 25 Dec 2024 01:29:17 +0000 Subject: [PATCH 4/4] fix: updating formatting for scheduled published releases date --- .../src/core/releases/tool/detail/ReleaseTypePicker.tsx | 2 +- .../tool/detail/__tests__/ReleaseTypePicker.test.tsx | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx b/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx index 8cccb34e5c7..77ad23f985a 100644 --- a/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx +++ b/packages/sanity/src/core/releases/tool/detail/ReleaseTypePicker.tsx @@ -97,7 +97,7 @@ export function ReleaseTypePicker(props: {release: ReleaseDocument}): JSX.Elemen if (release.state === 'published') { if (isPublishDateInPast && release.publishAt) return tRelease('dashboard.details.published-on', { - date: format(new Date(publishDate), 'MMM d, yyyy'), + date: format(new Date(publishDate), 'MMM d, yyyy, pp'), }) return tRelease('dashboard.details.published-asap') diff --git a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx index ed67181c39e..4d6c1ac769c 100644 --- a/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx +++ b/packages/sanity/src/core/releases/tool/detail/__tests__/ReleaseTypePicker.test.tsx @@ -86,7 +86,7 @@ describe('ReleaseTypePicker', () => { expect(screen.getByTestId('published-release-type-label')).toBeInTheDocument() - expect(screen.getByText('Published on Oct 10, 2023')).toBeInTheDocument() + expect(screen.getByText('Published on Oct 10, 2023, 3:00:00 AM')).toBeInTheDocument() }) }) @@ -148,8 +148,10 @@ describe('ReleaseTypePicker', () => { const Calendar = getByDataUi(document.body, 'CalendarMonth') - // Select the 10th day in the calendar + // Select the 10th day in the calendar month fireEvent.click(within(Calendar).getByText('10')) + fireEvent.change(screen.getByLabelText('Select hour'), {target: {value: 10}}) + fireEvent.change(screen.getByLabelText('Select minute'), {target: {value: 55}}) expect(mockUpdateRelease).not.toHaveBeenCalled() // Close the popup and check if the release is updated @@ -160,7 +162,8 @@ describe('ReleaseTypePicker', () => { metadata: expect.objectContaining({ ...activeASAPRelease.metadata, releaseType: 'scheduled', - intendedPublishAt: expect.stringMatching(/^\d{4}-\d{2}-10T\d{2}:\d{2}:\d{2}\.\d{3}Z$/), + /** @todo improve the assertion on the dateTime */ + intendedPublishAt: expect.stringMatching(/^\d{4}-\d{2}-\d{2}T\d{2}:55:\d{2}\.\d{3}Z$/), }), }) })