Skip to content

Commit

Permalink
feat(tasks): add notification data for tasks document and tasks comme…
Browse files Browse the repository at this point in the history
…nts (#5998)

* feat(tasks): add notification target to task document

* feat(tasks): add notification context to tasks comments

* fix(tasks): add comment id to the comments notification url

* fix(tasks): add TasksNotificationTarget by the tasks schema

* fix(tasks): remove onChange action from handleGetNotificationValue function

* fix(tasks): update deep equal comparison
  • Loading branch information
pedrobonamin committed Mar 14, 2024
1 parent 96bc72b commit 8e63552
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 5 deletions.
Expand Up @@ -78,7 +78,7 @@ export async function createOperation(props: CreateOperationProps): Promise<void
payload: {
workspace,
},
notification: undefined, // TODO: add task notification data
notification: comment.context.notification,
tool: activeTool?.name || '',
},

Expand Down
5 changes: 5 additions & 0 deletions packages/sanity/src/structure/comments/src/types.ts
Expand Up @@ -101,6 +101,8 @@ export interface CommentContext {
url: string
workspaceTitle: string
currentThreadLength?: number
// Used in task comments, list of users that are subscribed to the task.
subscribers?: string[]
}
intent?: {
title: string
Expand Down Expand Up @@ -260,6 +262,9 @@ export interface CommentBaseCreatePayload {
export interface CommentTaskCreatePayload extends CommentBaseCreatePayload {
// ...
type: 'task'
context: {
notification: CommentContext['notification']
}
}

/**
Expand Down
Expand Up @@ -8,9 +8,11 @@ import {
LoadingBlock,
type PatchEvent,
type Path,
set,
type TransactionLogEventWithEffects,
useClient,
useCurrentUser,
useWorkspace,
} from 'sanity'
import styled from 'styled-components'

Expand All @@ -29,6 +31,7 @@ import {
import {API_VERSION} from '../../constants/API_VERSION'
import {type TaskDocument} from '../../types'
import {CurrentWorkspaceProvider} from '../form/CurrentWorkspaceProvider'
import {getMentionedUsers} from '../form/utils'
import {type FieldChange, trackFieldChanges} from './helpers/parseTransactions'
import {EditedAt} from './TaskActivityEditedAt'
import {TasksActivityCommentInput} from './TasksActivityCommentInput'
Expand Down Expand Up @@ -134,39 +137,80 @@ type Activity =
export function TasksActivityLog(props: TasksActivityLogProps) {
const {value, onChange, path} = props
const currentUser = useCurrentUser()
const {title: workspaceTitle, basePath} = useWorkspace()

const {comments, mentionOptions, operation, getComment} = useComments()
const loading = comments.loading
const taskComments = comments.data.open

const handleGetNotificationValue = useCallback(
(message: CommentInputProps['value'], commentId: string) => {
const studioUrl = new URL(`${window.location.origin}${basePath}/`)
studioUrl.searchParams.set('sidebar', 'tasks')
studioUrl.searchParams.set('selectedTask', value?._id)
studioUrl.searchParams.set('viewMode', 'edit')
studioUrl.searchParams.set('commentId', commentId)

const mentionedUsers = getMentionedUsers(message)
const subscribers = Array.from(new Set([...(value.subscribers || []), ...mentionedUsers]))

return {
documentTitle: value.title || 'Sanity task',
url: studioUrl.toString(),
workspaceTitle: workspaceTitle,
subscribers: subscribers,
}
},
[basePath, value?._id, value.title, workspaceTitle, value.subscribers],
)

const handleCommentCreate = useCallback(
(message: CommentInputProps['value']) => {
const commentId = uuid()
const notification = handleGetNotificationValue(message, commentId)

const nextComment: CommentCreatePayload = {
id: commentId,
type: 'task',
message,
parentCommentId: undefined,
reactions: EMPTY_ARRAY,
status: 'open',
threadId: uuid(),
context: {
notification,
},
}

onChange(set(notification.subscribers, ['subscribers']))

operation.create(nextComment)
},
[operation],
[operation, handleGetNotificationValue, onChange],
)

const handleCommentReply = useCallback(
(nextComment: CommentBaseCreatePayload) => {
const commentId = uuid()

const notification = handleGetNotificationValue(nextComment.message, commentId)

onChange(set(notification.subscribers, ['subscribers']))

operation.create({
id: commentId,
type: 'task',
message: nextComment.message,
parentCommentId: nextComment.parentCommentId,
reactions: EMPTY_ARRAY,
status: 'open',
threadId: nextComment.threadId,
context: {
notification,
},
})
},
[operation],
[operation, handleGetNotificationValue, onChange],
)

const handleCommentCreateRetry = useCallback(
Expand All @@ -176,6 +220,10 @@ export function TasksActivityLog(props: TasksActivityLogProps) {
const comment = getComment(id)
if (!comment) return

const notification = handleGetNotificationValue(comment.message, comment._id)

onChange(set(notification.subscribers, ['subscribers']))

operation.create({
type: 'task',
id: comment._id,
Expand All @@ -184,9 +232,12 @@ export function TasksActivityLog(props: TasksActivityLogProps) {
reactions: comment.reactions || EMPTY_ARRAY,
status: comment.status,
threadId: comment.threadId,
context: {
notification,
},
})
},
[getComment, operation],
[getComment, operation, handleGetNotificationValue, onChange],
)

const handleCommentReact = useCallback(
Expand Down
Expand Up @@ -11,6 +11,7 @@ import {
} from '../fields'
import {FormCreate} from '../tasksFormBuilder/FormCreate'
import {FormEdit} from '../tasksFormBuilder/FormEdit'
import {TasksNotificationTarget} from '../tasksFormBuilder/TasksNotificationTarget'

const targetContentField = (mode: FormMode) =>
defineField({
Expand Down Expand Up @@ -144,5 +145,36 @@ export const taskSchema = (mode: FormMode) =>
},
hidden: true,
},
{
type: 'object',
name: 'context',
components: {
field: TasksNotificationTarget,
},
fields: [
{
type: 'object',
name: 'notification',
fields: [
{
type: 'string',
name: 'url',
},
{
type: 'string',
name: 'workspaceTitle',
},
{
type: 'string',
name: 'targetContentImageUrl',
},
{
type: 'string',
name: 'targetContentTitle',
},
],
},
],
},
],
})
@@ -0,0 +1,80 @@
import {isImageSource} from '@sanity/asset-utils'
import imageUrlBuilder from '@sanity/image-url'
import {useEffect, useMemo} from 'react'
import deepEquals from 'react-fast-compare'
import {
DEFAULT_STUDIO_CLIENT_OPTIONS,
type ObjectFieldProps,
set,
useClient,
useFormValue,
useWorkspace,
} from 'sanity'

import {useDocumentPreviewValues} from '../../../hooks/useDocumentPreviewValues'
import {type TaskContext, type TaskDocument} from '../../../types'
import {CurrentWorkspaceProvider} from '../CurrentWorkspaceProvider'

function TasksNotificationTargetInner(props: ObjectFieldProps<TaskDocument>) {
const {inputProps} = props
const {onChange} = inputProps
const {target, _id, context} = useFormValue([]) as TaskDocument
const {title: workspaceTitle, basePath} = useWorkspace()
const client = useClient(DEFAULT_STUDIO_CLIENT_OPTIONS)
const imageBuilder = useMemo(() => imageUrlBuilder(client), [client])
const documentId = target?.document?._ref ?? ''
const documentType = target?.documentType ?? ''

const {isLoading: previewValuesLoading, value} = useDocumentPreviewValues({
documentId,
documentType,
})
const targetContentTitle = value?.title || null
const imageUrl = isImageSource(value?.media)
? imageBuilder.image(value.media).width(96).height(96).url()
: null

useEffect(() => {
if (documentId && documentType && previewValuesLoading) {
// Wait until the preview values are loaded
return
}

const studioUrl = new URL(`${window.location.origin}${basePath}/`)
studioUrl.searchParams.set('sidebar', 'tasks')
studioUrl.searchParams.set('selectedTask', _id)
studioUrl.searchParams.set('viewMode', 'edit')

const notificationTarget: TaskContext['notification'] = {
url: studioUrl.toString(),
workspaceTitle,
targetContentImageUrl: imageUrl,
targetContentTitle: targetContentTitle,
}
if (deepEquals(notificationTarget, context?.notification)) return

onChange(set(notificationTarget, ['notification']))
}, [
_id,
basePath,
workspaceTitle,
documentId,
documentType,
previewValuesLoading,
targetContentTitle,
imageUrl,
onChange,
context,
])

return null
}

// This component is listening to the changes to the form value and will update the notification target in the task document.
export function TasksNotificationTarget(props: ObjectFieldProps<TaskDocument>) {
return (
<CurrentWorkspaceProvider>
<TasksNotificationTargetInner {...props} />
</CurrentWorkspaceProvider>
)
}
3 changes: 2 additions & 1 deletion packages/sanity/src/tasks/src/tasks/types.ts
Expand Up @@ -30,9 +30,10 @@ export interface TaskContext {
tool?: string
payload?: Record<string, unknown>
notification?: {
documentTitle: string
url: string
workspaceTitle: string
targetContentTitle: string | null
targetContentImageUrl: string | null
}
}

Expand Down

0 comments on commit 8e63552

Please sign in to comment.