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): using publishedAt to present publish time for past releases #8553

Merged
merged 4 commits into from
Feb 10, 2025
Merged
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
@@ -66,6 +66,7 @@ export const publishedASAPRelease: ReleaseDocument = {
_type: 'system.release',
_createdAt: '2023-10-10T08:00:00Z',
_updatedAt: '2023-10-10T09:00:00Z',
publishedAt: '2023-10-10T10:00:00Z',
state: 'published',
metadata: {
title: 'published Release',
28 changes: 28 additions & 0 deletions packages/sanity/src/core/releases/hooks/useReleaseTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {format} from 'date-fns'
import {useCallback} from 'react'

import useTimeZone, {getLocalTimeZone} from '../../scheduledPublishing/hooks/useTimeZone'
import {type TableRelease} from '../tool/overview/ReleasesOverview'
import {getPublishDateFromRelease} from '../util/util'

export const useReleaseTime = (): ((release: TableRelease) => string | null) => {
const {timeZone, utcToCurrentZoneDate} = useTimeZone()
const {abbreviation: localeTimeZoneAbbreviation} = getLocalTimeZone()

const getTimezoneAbbreviation = useCallback(
() =>
timeZone.abbreviation === localeTimeZoneAbbreviation ? '' : `(${timeZone.abbreviation})`,
[localeTimeZoneAbbreviation, timeZone.abbreviation],
)

return useCallback(
(release: TableRelease) => {
const publishDate = getPublishDateFromRelease(release)

return publishDate
? `${format(utcToCurrentZoneDate(publishDate), 'PPpp')} ${getTimezoneAbbreviation()}`
: null
},
[getTimezoneAbbreviation, utcToCurrentZoneDate],
)
}
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ const QUERY_PROJECTION = `{
state,
finalDocumentStates,
publishAt,
publishedAt,
"metadata": coalesce(metadata, {
"title": "",
"releaseType": "${DEFAULT_RELEASE_TYPE}",
4 changes: 4 additions & 0 deletions packages/sanity/src/core/releases/store/types.ts
Original file line number Diff line number Diff line change
@@ -46,6 +46,10 @@ export interface ReleaseDocument extends SanityDocument {
* If defined, it takes precedence over the intendedPublishAt, the state should be 'scheduled'
*/
publishAt?: string
/**
* If defined, it provides the time the release was actually published
*/
publishedAt?: string
metadata: {
title: string
description?: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {useTranslation} from '../../../i18n'
import {useReleaseTime} from '../../hooks/useReleaseTime'
import {type TableRelease} from '../overview/ReleasesOverview'

export const ReleaseTime: React.FC<{release: TableRelease}> = ({release}) => {
const {t} = useTranslation()
const getReleaseTime = useReleaseTime()

const {metadata} = release

if (metadata.releaseType === 'asap') {
return t('release.type.asap')
}
if (metadata.releaseType === 'undecided') {
return t('release.type.undecided')
}

return getReleaseTime(release)
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import {render, screen, waitFor} from '@testing-library/react'
import {format} from 'date-fns'
import {type ComponentProps} from 'react'
import {describe, expect, it, vi} from 'vitest'

import {createTestProvider} from '../../../../../../../test/testUtils/TestProvider'
import {useTimeZoneMockReturn} from '../../../../../scheduledPublishing/hooks/__tests__/__mocks__/useTimeZone.mock'
import {getByDataUi} from '../../../../../../test/setup/customQueries'
import {createTestProvider} from '../../../../../../test/testUtils/TestProvider'
import {useTimeZoneMockReturn} from '../../../../scheduledPublishing/hooks/__tests__/__mocks__/useTimeZone.mock'
import {
activeASAPRelease,
activeUndecidedRelease,
scheduledRelease,
} from '../../../../__fixtures__/release.fixture'
import {releasesUsEnglishLocaleBundle} from '../../../../i18n'
import {ReleaseTime} from '../../columnCells/ReleaseTime'
import {type TableRelease} from '../../ReleasesOverview'
} from '../../../__fixtures__/release.fixture'
import {releasesUsEnglishLocaleBundle} from '../../../i18n'
import {type TableRelease} from '../../overview/ReleasesOverview'
import {ReleaseTime} from '../ReleaseTime'

vi.mock('../../../../scheduledPublishing/hooks/useTimeZone', () => useTimeZoneMockReturn)
vi.mock(
'../../../scheduledPublishing/hooks/useTimeZone',
vi.fn(() => useTimeZoneMockReturn),
)

const renderTest = async (props: ComponentProps<typeof ReleaseTime>) => {
const wrapper = await createTestProvider({
@@ -56,11 +59,11 @@ describe('ReleaseTime', () => {
release: {
...scheduledRelease,
publishAt: undefined,
publishedAt: undefined,
metadata: {...scheduledRelease.metadata, intendedPublishAt: undefined},
} as TableRelease,
})

const formattedDate = `${format(new Date(), 'PPpp')}`
expect(screen.getByText(formattedDate, {exact: false})).toBeInTheDocument()
expect(getByDataUi(document.body, 'Box')).toBeEmptyDOMElement()
})
})
Original file line number Diff line number Diff line change
@@ -13,13 +13,14 @@ import {getCalendarLabels} from '../../../form/inputs/DateInputs/utils'
import {useTranslation} from '../../../i18n/hooks/useTranslation'
import useTimeZone from '../../../scheduledPublishing/hooks/useTimeZone'
import {ReleaseAvatar} from '../../components/ReleaseAvatar'
import {useReleaseTime} from '../../hooks/useReleaseTime'
import {releasesLocaleNamespace} from '../../i18n'
import {type ReleaseDocument, type ReleaseType} from '../../store'
import {useReleaseOperations} from '../../store/useReleaseOperations'
import {getIsScheduledDateInPast} from '../../util/getIsScheduledDateInPast'
import {getReleaseTone} from '../../util/getReleaseTone'
import {getPublishDateFromRelease, isReleaseScheduledOrScheduling} from '../../util/util'
import {ReleaseTime} from '../overview/columnCells/ReleaseTime'
import {ReleaseTime} from '../components/ReleaseTime'

const dateInputFormat = 'PP HH:mm'

@@ -36,6 +37,7 @@ export function ReleaseTypePicker(props: {release: ReleaseDocument}): React.JSX.
const {updateRelease} = useReleaseOperations()
const toast = useToast()
const {utcToCurrentZoneDate, zoneDateToUtc} = useTimeZone()
const getReleaseTime = useReleaseTime()

const [open, setOpen] = useState(false)
const [releaseType, setReleaseType] = useState<ReleaseType>(release.metadata.releaseType)
@@ -108,16 +110,16 @@ export function ReleaseTypePicker(props: {release: ReleaseDocument}): React.JSX.

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

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

return <ReleaseTime release={release} />
}, [isPublishDateInPast, publishDate, release, tRelease])
}, [getReleaseTime, isPublishDateInPast, publishDate, release, tRelease])

const handleButtonReleaseTypeChange = useCallback((pickedReleaseType: ReleaseType) => {
setReleaseType(pickedReleaseType)
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ describe('ReleaseTypePicker', () => {

expect(screen.getByTestId('published-release-type-label')).toBeInTheDocument()

expect(screen.getByText('Published')).toBeInTheDocument()
expect(screen.getByText('Published on Oct 10, 2023, 3:00:00 AM')).toBeInTheDocument()
})

it('renders the label with a published text when release was schedule published', async () => {
Original file line number Diff line number Diff line change
@@ -7,11 +7,11 @@ import {ToneIcon} from '../../../../ui-components/toneIcon/ToneIcon'
import {Tooltip} from '../../../../ui-components/tooltip/Tooltip'
import {RelativeTime} from '../../../components'
import {getPublishDateFromRelease, isReleaseScheduledOrScheduling} from '../../util/util'
import {ReleaseTime} from '../components/ReleaseTime'
import {Headers} from '../components/Table/TableHeader'
import {type Column} from '../components/Table/types'
import {ReleaseDocumentsCounter} from './columnCells/ReleaseDocumentsCounter'
import {ReleaseNameCell} from './columnCells/ReleaseName'
import {ReleaseTime} from './columnCells/ReleaseTime'
import {type TableRelease} from './ReleasesOverview'

export const releasesOverviewColumnDefs: (

This file was deleted.

5 changes: 2 additions & 3 deletions packages/sanity/src/core/releases/util/util.ts
Original file line number Diff line number Diff line change
@@ -37,10 +37,9 @@ export function isDraftOrPublished(versionName: string): boolean {

/** @internal */
export function getPublishDateFromRelease(release: ReleaseDocument): Date | null {
if (release.metadata.releaseType !== 'scheduled') return null
const dateString = release.publishedAt || release.publishAt || release.metadata.intendedPublishAt

const dateString = release.publishAt || release.metadata.intendedPublishAt
if (!dateString) return new Date()
if (!dateString) return null

return new Date(dateString)
}

Unchanged files with check annotations Beta

)
const debounceSelectionChange = useMemo(
() => debounce(handleSelectionChange, 200),

Check warning on line 262 in packages/sanity/src/core/comments/plugin/input/components/CommentsPortableTextInput.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
[handleSelectionChange],
)
const currentSelectionIsOverlapping = useMemo(() => {
if (!currentSelection || addedCommentsDecorations.length === 0) return false
return addedCommentsDecorations.some((d) => {

Check warning on line 441 in packages/sanity/src/core/comments/plugin/input/components/CommentsPortableTextInput.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
if (!editorRef.current) return false
const testA = PortableTextEditor.isSelectionsOverlapping(
}, [handleBuildRangeDecorations, textComments])
const showFloatingButton = Boolean(
currentSelection && canSubmit && selectionReferenceElement && !mouseDownRef.current,

Check warning on line 528 in packages/sanity/src/core/comments/plugin/input/components/CommentsPortableTextInput.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
)
const showFloatingInput = Boolean(nextCommentSelection && popoverAuthoringReferenceElement)
</BoundaryElementProvider>
<Stack ref={setRootElement} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp}>
{props.renderDefault({

Check warning on line 561 in packages/sanity/src/core/comments/plugin/input/components/CommentsPortableTextInput.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
...props,
onEditorChange,
editorRef,
const disabled = getItemDisabled?.(index) ?? false
const selected = getItemSelected?.(index) ?? false
if (!disabled) {
i += 1

Check warning on line 185 in packages/sanity/src/core/components/commandList/CommandList.tsx

GitHub Actions / lint

Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
}
acc[index] = {
activeIndex: disabled ? null : i,
const rangeDecorations = useMemo((): RangeDecoration[] => {
const result = [...(rangeDecorationsProp || []), ...presenceCursorDecorations]
const reconciled = immutableReconcile(previousRangeDecorations.current, result)

Check warning on line 296 in packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
previousRangeDecorations.current = reconciled

Check warning on line 297 in packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
return reconciled
}, [presenceCursorDecorations, rangeDecorationsProp])
[validation],
)
const reconciled = immutableReconcile(prev.current, validation)

Check warning on line 36 in packages/sanity/src/core/form/inputs/PortableText/hooks/useMemberValidation.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
prev.current = reconciled

Check warning on line 37 in packages/sanity/src/core/form/inputs/PortableText/hooks/useMemberValidation.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
return useMemo(() => {
return {
}
}
const items: PortableTextMemberItem[] = result.map((item) => {

Check warning on line 121 in packages/sanity/src/core/form/inputs/PortableText/hooks/usePortableTextMembers.tsx

GitHub Actions / lint

Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
const key = pathToString(item.node.path)
const existingItem = portableTextMemberItemsRef.current.find((refItem) => refItem.key === key)
const isObject = item.kind !== 'textBlock'
import {DefaultDocument} from 'sanity'
export default function Document(props) {

Check warning on line 3 in dev/strict-studio/_document.tsx

GitHub Actions / lint

Missing return type on function.

Check warning on line 3 in dev/strict-studio/_document.tsx

GitHub Actions / lint

Argument 'props' should be typed.
const {entryPath, ...rest} = props

Check warning on line 4 in dev/strict-studio/_document.tsx

GitHub Actions / lint

'entryPath' is assigned a value but never used.

Check warning on line 4 in dev/strict-studio/_document.tsx

GitHub Actions / lint

'entryPath' is assigned a value but never used.
return <DefaultDocument entryPath="/entry.tsx" {...rest} />
}
export function diffNumber<A>(
fromInput: NumberInput<A>,
toInput: NumberInput<A>,
options: DiffOptions,

Check warning on line 12 in packages/@sanity/diff/src/calculate/diffSimple.ts

GitHub Actions / lint

'options' is defined but never used.

Check warning on line 12 in packages/@sanity/diff/src/calculate/diffSimple.ts

GitHub Actions / lint

'options' is defined but never used.
): NumberDiff<A> {
const fromValue = fromInput.value
const toValue = toInput.value
export function diffBoolean<A>(
fromInput: BooleanInput<A>,
toInput: BooleanInput<A>,
options: DiffOptions,

Check warning on line 40 in packages/@sanity/diff/src/calculate/diffSimple.ts

GitHub Actions / lint

'options' is defined but never used.

Check warning on line 40 in packages/@sanity/diff/src/calculate/diffSimple.ts

GitHub Actions / lint

'options' is defined but never used.
): BooleanDiff<A> {
const fromValue = fromInput.value
const toValue = toInput.value
export function diffString<A>(
fromInput: StringInput<A>,
toInput: StringInput<A>,
options: DiffOptions,

Check warning on line 15 in packages/@sanity/diff/src/calculate/diffString.ts

GitHub Actions / lint

'options' is defined but never used.

Check warning on line 15 in packages/@sanity/diff/src/calculate/diffString.ts

GitHub Actions / lint

'options' is defined but never used.
): StringDiff<A> {
const fromValue = fromInput.value
const toValue = toInput.value