useDocyrusContactActivity
Merge a Docyrus record's audit log, threaded comments, related events, tasks, and status updates into one timeline for <ContactActivityPanel />, plus comment and status mutations.
Installation
pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-contact-activitypnpm add @docyrus/api-client @tanstack/react-queryThe hook needs an authenticated RestApiClient from @docyrus/api-client and a QueryClientProvider
above your component tree. Read endpoints require the relevant DS.Read.* scopes; comment writes and
status updates require DS.ReadWrite.* on the record's data source.
Overview
useDocyrusContactActivity is the Docyrus-backed engine behind ContactActivityPanel.
It runs up to six React Query sources scoped to one record and merges them into a single
date-sorted ContactActivity[] feed:
- Audit —
GET …/items/:id/activities: CRUD + file trace. Comment operations (INSERT_COMMENT/UPDATE_COMMENT/DELETE_COMMENT) are filtered out here because they arrive threaded from the comments source (hybrid, dedup-safe). - Comments —
GET …/items/:id/comments: the flat comment list, rebuilt into aparent_idtree (arbitrary depth). Top-level comments become timeline entries;onLoadRepliesreturns all descendants. - Events — related
base/eventrows (needsrelationSlug/eventRelationSlug), classifiedcallvsmeetingfrom thecalendarevent-type name. - Tasks — related
base/taskrows (needsrelationSlug/taskRelationSlug). - Status updates —
base/activityrows wherestatus_update_record_id = recordId, surfaced as first-classstatus_updateentries. - Files —
GET …/items/:id/files: documents attached to the record, surfaced asfileentries carrying a realPostAttachment(name, size, MIME, signed URL). With this source on, the audit log's bareUPLOAD_FILEtraces are dropped — they describe the same upload with less information.
A status change goes through the two-call updateStatus flow — it PATCHes the record (which emits a
generic audit UPDATE) and logs a status_update activity. The merge suppresses the audit
record_update row that coincides with a status update (short time window) so a single change surfaces
once, not twice.
The primary output is panelProps — spread it straight onto <ContactActivityPanel />. The panel
stays presentational; the hook owns all I/O.
Usage
import { useDocyrusContactActivity } from '@docyrus/ui/library/hooks/use-docyrus-contact-activity';
import { ContactActivityPanel } from '@docyrus/ui/components/contact-activity-panel';
function RecordTimeline({ client, recordId }) {
const activity = useDocyrusContactActivity({
client,
appSlug: 'base',
dataSourceSlug: 'contact',
recordId,
// Per-tenant back-relation slug on base/event + base/task — required for those sources.
relationSlug: 'contact',
currentUserId: myUserId
});
return <ContactActivityPanel {...activity.panelProps} />;
}Options
useDocyrusContactActivity(options: UseDocyrusContactActivityOptions)
| Option | Type | Default | Description |
|---|---|---|---|
client | ContactActivityClient | — | Authenticated REST client (RestApiClient satisfies it). |
appSlug | string | — | App slug of the owning record. |
dataSourceSlug | string | — | Data source slug of the owning record. |
recordId | string | — | Owning record id. |
relationSlug | string | — | Back-relation slug on base/event + base/task pointing at this record (per-tenant, e.g. contact). Required to include the events / tasks sources. |
eventRelationSlug | string | relationSlug | Override the event back-relation slug. |
taskRelationSlug | string | relationSlug | Override the task back-relation slug. |
sources | ContactActivitySources | all true | Toggle which sources merge (audit / comments / events / tasks / statusUpdates / files). |
eventAppSlug / eventDataSourceSlug | string | base / event | Events source override. |
taskAppSlug / taskDataSourceSlug | string | base / task | Tasks source override. |
activityAppSlug / activityDataSourceSlug | string | base / activity | Status-activity source override. |
users | ChatUser[] | — | Consumer-supplied users (preferred). Falls back to /v1/users when omitted. |
currentUserId | string | — | Flags the viewer's own activities. |
activityTypes | ActivityType[] | — | Restrict the panel's type-filter toggles. |
eventTypeNames | EventTypeNameHints | call: ['call','phone'], meeting: ['meeting','visit','demo'] | Case-insensitive name hints for call vs meeting classification. |
classifyEvent | (event) => 'call' | 'meeting' | — | Custom classifier (wins over eventTypeNames). |
relatedColumns | { events?: string[]; tasks?: string[] } | — | Extra relation columns fetched on the event / task sources and surfaced as related chips. Relation slugs are per-tenant and an unknown column fails the whole request, so nothing is requested unless you name it. |
limit | number | 100 | Per-source page size (the API caps at 100 for OAuth tokens). |
enabled | boolean | true once recordId is present | Gate every query. |
Returns
UseDocyrusContactActivityResult
| Field | Type | Description |
|---|---|---|
activities | ContactActivity[] | Merged, date-sorted timeline. |
users | ChatUser[] | Resolved users. |
currentUser | ChatUser | undefined | Resolved current user. |
panelProps | ContactActivityPanelData | Spread onto <ContactActivityPanel />. |
isLoading | boolean | Any read source loading. |
error | Error | null | First read-source error. |
isMutating | boolean | Any mutation pending. |
mutationError | Error | null | First mutation error. |
refetch | () => Promise<unknown> | Invalidate the record-scoped keys. |
createComment | (payload: CreatePostPayload) => Promise<void> | Post a comment (threaded reply via parent_id). |
deleteComment | (commentId: string) => Promise<void> | Delete a comment by id. |
updateStatus | (input: UpdateStatusInput) => Promise<void> | Two-call status change (see below). |
isUpdatingStatus | boolean | Status mutation pending. |
Two-call status update
updateStatus performs the record PATCH then logs the status-update activity:
await activity.updateStatus({
statusFieldSlug: 'status',
value: enumId,
secondaryValue, // → __status_secondary (only sent when provided)
description, // → __status_description
followupDate // → __status_followup_date
});Companion fields (__<slug>_*) are only sent when provided, so plain field-select status fields
(which have no companion columns) don't 400. If the record PATCH succeeds but logging fails, the promise
rejects with a partial-failure error (error.partial === true) — the record did change, and the
timeline still invalidates (onSettled) so the change is reflected. Surface this to the user so a retry
isn't mistaken for a no-op.
Notes
- Users are shared. All Docyrus activity hooks fetch
/v1/usersunder one query key (['docyrus-users', limit]) and the same canonical shape, so React Query dedupes to a single request across this hook,useDocyrusCreateTask, anduseDocyrusEventCreate. - Rate-limit aware. Read sources carry a short
staleTimeso tab switches / refocus don't refire the 5-source fan-out against the tight API rate limit. - Files are on by default. The documents source adds one request per record; pass
sources={{ files: false }}to keep the previous behaviour (uploads then stay as bare audit traces). - Related chips. A comment's
assigned_tobecomes a related chip automatically (resolved against the users list). Everything else is opt-in throughrelatedColumns— the hook never guesses a tenant's relation slugs. - Per-tenant relations. On some tenants
base/event ↔ contactis many-to-many with no scalar column — pass the correct scalar back-relation viaeventRelationSlug/taskRelationSlug, or omit them to leave those sources off.
Type Exports
| Type | Description |
|---|---|
UseDocyrusContactActivityOptions | Hook options. |
UseDocyrusContactActivityResult | Hook return. |
ContactActivityPanelData | The panelProps bag. |
ContactActivitySources | Per-source toggles. |
EventTypeNameHints | Call / meeting classification hints. |
UpdateStatusInput | updateStatus payload. |
ContactActivityClient | Minimal REST surface the hook needs. |