$fieldValueAttributes */ public function handle( Ticket $ticket, User $actor, array $ticketAttributes, array $fieldValueAttributes, ): bool { return DB::transaction(function () use ($actor, $fieldValueAttributes, $ticket, $ticketAttributes): bool { if (! $ticketAttributes['is_blocked']) { $ticketAttributes['blocked_comment'] = null; } $ticket->loadMissing([ 'assignee', 'author', 'fieldValues.ticketFieldDefinition', 'fieldValues.userValue', 'parentTicket', 'project', 'ticketPriority', 'ticketType', ]); /** @var Collection $fieldDefinitions */ $fieldDefinitions = $ticket->project->ticketFieldDefinitions() ->get() ->keyBy('id'); $ticketTypeOptions = $ticket->project->ticketTypes() ->get() ->keyBy('id'); $ticketPriorityOptions = $ticket->project->ticketPriorities() ->get() ->keyBy('id'); /** @var Collection $existingFieldValues */ $existingFieldValues = $ticket->fieldValues ->keyBy('ticket_field_definition_id'); $changes = []; $summaryLabels = []; $this->trackScalarChange( $changes, $summaryLabels, 'title', 'title', $ticket->title, $ticketAttributes['title'], ); $this->trackScalarChange( $changes, $summaryLabels, 'description', 'description', $ticket->description, $ticketAttributes['description'], ); $this->trackScalarChange( $changes, $summaryLabels, 'acceptance_criteria', 'acceptance criteria', $ticket->acceptance_criteria, $ticketAttributes['acceptance_criteria'], ); $this->trackScalarChange( $changes, $summaryLabels, 'blocked_comment', 'blocker comment', $ticket->blocked_comment, $ticketAttributes['blocked_comment'], ); $this->trackNumberChange( $changes, $summaryLabels, 'story_points', 'story points', $ticket->story_points, $ticketAttributes['story_points'], ); if ($ticket->ticket_type_id !== $ticketAttributes['ticket_type_id']) { $changes['ticket_type'] = [ 'from' => $ticket->ticketType?->name, 'to' => $ticketTypeOptions->get($ticketAttributes['ticket_type_id'])?->name, ]; $summaryLabels[] = 'type'; } if ($ticket->ticket_priority_id !== $ticketAttributes['ticket_priority_id']) { $changes['ticket_priority'] = [ 'from' => $ticket->ticketPriority?->name, 'to' => $ticketPriorityOptions->get($ticketAttributes['ticket_priority_id'])?->name, ]; $summaryLabels[] = 'priority'; } if ($ticket->is_blocked !== $ticketAttributes['is_blocked']) { $changes['is_blocked'] = [ 'from' => $ticket->is_blocked, 'to' => $ticketAttributes['is_blocked'], ]; $summaryLabels[] = 'blocked state'; } if ($ticket->parent_ticket_id !== $ticketAttributes['parent_ticket_id']) { $newParentTicket = $ticketAttributes['parent_ticket_id'] === null ? null : Ticket::query()->find($ticketAttributes['parent_ticket_id']); $changes['parent_ticket'] = [ 'from' => $ticket->parentTicket?->ticket_key, 'to' => $newParentTicket?->ticket_key, ]; $summaryLabels[] = 'parent ticket'; } if ($ticket->assignee_user_id !== $ticketAttributes['assignee_user_id']) { $changes['assignee'] = [ 'from' => $ticket->assignee?->name, 'to' => $ticketAttributes['assignee_user_id'] === null ? null : User::query()->find($ticketAttributes['assignee_user_id'])?->name, ]; $summaryLabels[] = 'assignee'; } if ($ticket->author_user_id !== $ticketAttributes['author_user_id']) { $changes['author'] = [ 'from' => $ticket->author?->name, 'to' => $ticketAttributes['author_user_id'] === null ? null : User::query()->find($ticketAttributes['author_user_id'])?->name, ]; $summaryLabels[] = 'author'; } $ticket->fill($ticketAttributes); if ($ticket->isDirty()) { $ticket->save(); } $fieldValueChangeSet = $this->syncProjectTicketFieldValues->handle( $ticket, $fieldDefinitions, $existingFieldValues, $fieldValueAttributes, ); if ($fieldValueChangeSet['changes'] !== []) { $changes['field_values'] = $fieldValueChangeSet['changes']; $summaryLabels = [ ...$summaryLabels, ...$fieldValueChangeSet['summary_labels'], ]; } if ($summaryLabels === []) { return false; } $this->createTicketWorklogEntry->handle( $ticket->fresh(), $actor, TicketWorklogEntryType::Updated, $this->buildSummary($summaryLabels), $changes, ); return true; }); } /** * @param array $changes * @param array $summaryLabels */ private function trackScalarChange( array &$changes, array &$summaryLabels, string $changeKey, string $summaryLabel, ?string $currentValue, ?string $newValue, ): void { if ($currentValue === $newValue) { return; } $changes[$changeKey] = [ 'from' => $currentValue, 'to' => $newValue, ]; $summaryLabels[] = $summaryLabel; } /** * @param array $changes * @param array $summaryLabels */ private function trackNumberChange( array &$changes, array &$summaryLabels, string $changeKey, string $summaryLabel, ?int $currentValue, ?int $newValue, ): void { if ($currentValue === $newValue) { return; } $changes[$changeKey] = [ 'from' => $currentValue, 'to' => $newValue, ]; $summaryLabels[] = $summaryLabel; } /** * @param array $summaryLabels */ private function buildSummary(array $summaryLabels): string { $uniqueLabels = collect($summaryLabels) ->unique() ->values(); if ($uniqueLabels->count() === 1) { return 'Updated '.$uniqueLabels->first().'.'; } if ($uniqueLabels->count() === 2) { return 'Updated '.$uniqueLabels->first().' and '.$uniqueLabels->last().'.'; } $lastLabel = $uniqueLabels->pop(); return 'Updated '.$uniqueLabels->implode(', ').', and '.$lastLabel.'.'; } }