|
1636030
|
15029
|
accessibility
|
AXButton
|
Previous Highlighted Error
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
16
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1636031
|
15029
|
accessibility
|
AXButton
|
Next Highlighted Error
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
17
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1636032
|
15029
|
accessibility
|
AXTextArea
|
<?php
declare(strict_types=1);
namespace Jimi <?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
$rateLimiter->incrementRequestCount($activity->getCrm());
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}...
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
18
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":true,"is_selected":false,"role_description":"text entry area","value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Activity\\Import;\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\DTO\\ImportCall\\Call;\nuse Jiminny\\Component\\TranscriptionSummary\\Events\\TranscriptionAiSummaryReadyEvent;\nuse Jiminny\\Events\\Activities\\AiAutomation\\ActivityProspectAdded;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Participant;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\ActivityRepository;\nuse Jiminny\\Services\\Crm\\CrmObjectsResolver;\nuse Jiminny\\Services\\Crm\\ProspectSearchStrategyFactory;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Psr\\Log\\LoggerInterface;\n\nclass MatchCrmData implements ShouldQueue\n{\n use Dispatchable;\n use InteractsWithQueue;\n use Queueable;\n\n // AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.\n private const int MAX_DELAY = 43140;\n\n // Infrastructure allows max 3 retries (1 initial execution + 3 retries)\n public int $tries = 4;\n\n private Call $call;\n private int $activityId;\n private Team $team;\n private ServiceInterface $crmService;\n\n private LoggerInterface $logger;\n private array $logContext;\n\n public function __construct(Call $call, int $activityId)\n {\n $this->call = $call;\n $this->activityId = $activityId;\n\n $this->logContext = [\n 'activity_id' => $activityId,\n 'call_id' => $call->getCallId(),\n 'provider' => $call->getProvider(),\n ];\n\n $this->onQueue(Constants::QUEUE_DIALERS);\n }\n\n public function handle(\n CrmObjectsResolver $crmObjectsResolver,\n ProviderRegistry $providerRegistry,\n ProviderRateLimiter $rateLimiter,\n ActivityRepository $activityRepository,\n LoggerInterface $logger,\n Dispatcher $eventDispatcher,\n ): void {\n $this->logger = $logger;\n\n // Activity is already augmented with CRM data, no need to perform the same operation\n if ($this->call->isActivityUpdatedWithCrm()) {\n $this->logMessage('Skipping activity. Already updated with CRM data');\n\n return;\n }\n\n /** @var Activity $activity */\n $activity = $activityRepository->findById($this->activityId);\n\n try {\n $this->initialiseCrmService($activity, $providerRegistry);\n } catch (SocialAccountTokenInvalidException $exception) {\n $this->logMessage('Invalid token, retrying');\n $this->release(self::MAX_DELAY); // Try again tomorrow\n\n return;\n }\n\n $prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);\n if ($prospectSearchStrategy->ignoreCrmMatchData()) {\n // Ignore any associated opportunity\n $this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [\n 'activity_id' => $this->activityId,\n 'strategy' => get_class($prospectSearchStrategy),\n ]);\n\n return;\n }\n\n if (! $rateLimiter->canMakeRequest($activity->getCrm())) {\n $this->logMessage('Rate limit reached, retrying');\n $this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));\n\n return;\n }\n\n $this->logMessage('Resolving CRM objects');\n\n $crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);\n $rateLimiter->incrementRequestCount($activity->getCrm());\n\n if (empty($crmObjects)) {\n $this->logMessage('Could not resolve CRM objects, retrying');\n $this->release(3600);\n\n return;\n }\n\n [$lead, $account, $opportunity, $contact, $stage] = $crmObjects;\n\n $activity->update([\n 'lead_id' => $lead->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'account_id' => $account->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'stage_id' => $stage->id ?? null,\n ]);\n\n $activity->refresh();\n\n $eventDispatcher->dispatch(new ActivityProspectAdded(\n activity: $activity,\n eventSource: 'match-crm-data'\n ));\n\n if ($activity->getProspectName() !== null) {\n $activity->setTitleFromCallData($this->call);\n\n /** @var Participant $prospectParticipant */\n $prospectParticipant = $activity\n ->participants()\n ->where(function (Builder $query) use ($activity) {\n $query\n ->whereNull('user_id')\n ->orWhere('user_id', '!=', $activity->getUserId())\n ;\n })\n ->first()\n ;\n\n $activity->updateParticipantCrmData($crmObjects, $prospectParticipant);\n }\n\n $this->logMessage('Activity updated');\n\n $this->triggerSummaryPushIfReady($activity, $eventDispatcher);\n }\n\n private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void\n {\n if (! $activity->hasTranscriptionId()) {\n return;\n }\n\n if ($activity->hasProspectActivitySummaryLog()) {\n $this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');\n\n return;\n }\n\n $this->logMessage('Triggering summary push after CRM matching');\n $eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));\n }\n\n private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void\n {\n $this->team = $activity->getUser()->getTeam();\n $crmProviderName = $this->team->getCrmConfiguration()->getProviderName();\n\n $crmService = $providerRegistry->get($crmProviderName);\n $crmService->setUser($this->team->getOwner());\n $this->crmService = $crmService;\n\n $this->logContext['team'] = $this->team->getSlug();\n $this->logContext['team_id'] = $this->team->getId();\n }\n\n private function logMessage(string $message): void\n {\n $this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);\n }\n}"}...
|
1
|
|
1636037
|
15029
|
accessibility
|
AXStaticText
|
19
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
23
|
{"role_description":"text"}
|
1
|
|
1636038
|
15029
|
accessibility
|
AXButton
|
Previous Highlighted Error
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
24
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1636039
|
15029
|
accessibility
|
AXButton
|
Next Highlighted Error
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
25
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1636040
|
15029
|
accessibility
|
AXTextArea
|
[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG [2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {
"headers":{
"Date":["Thu,07 May 2026 14:21:15 GMT"],
"Content-Type":["application/json;charset=utf-8"],
"Transfer-Encoding":["chunked"],
"Connection":["keep-alive"],
"CF-Ray":["9f80deb8db60dc3a-SOF"],
"CF-Cache-Status":["DYNAMIC"],
"Strict-Transport-Security":["max-age=31536000; includeSubDomains; preload"],
"Vary":["origin,
accept-encoding"],
"access-control-allow-credentials":["false"],
"server-timing":["hcid;desc=\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\",
cfr;desc=\"9f80deb8e7c6dc3a-IAD\""],
"x-content-type-options":["nosniff"],
"x-hubspot-correlation-id":["019e02d0-6fd8-7812-bdba-885b7ccb3ee3"],
"Set-Cookie":["__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,
07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None"],
"Report-To":["{
\"endpoints\":[{
\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\"}],
\"group\":\"cf-nel\",
\"max_age\":604800}"],
"NEL":["{
\"success_fraction\":0.01,
\"report_to\":\"cf-nel\",
\"max_age\":604800}"],
"Server":["cloudflare"]}} {
"correlation_id":"95236535-ec98-4541-b92a-adfa73b69eab",
"trace_id":"c7ab8365-903f-46d4-9403-0e5b551e3545"}...
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
26
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"text entry area","value":"[2026-05-07 14:21:15] local.INFO: [Hubspot] DEBUG Getting headers {\n\"headers\":{\n\"Date\":[\"Thu,07 May 2026 14:21:15 GMT\"],\n \"Content-Type\":[\"application/json;charset=utf-8\"],\n \"Transfer-Encoding\":[\"chunked\"],\n \"Connection\":[\"keep-alive\"],\n \"CF-Ray\":[\"9f80deb8db60dc3a-SOF\"],\n \"CF-Cache-Status\":[\"DYNAMIC\"],\n \"Strict-Transport-Security\":[\"max-age=31536000; includeSubDomains; preload\"],\n \"Vary\":[\"origin,\n accept-encoding\"],\n \"access-control-allow-credentials\":[\"false\"],\n \"server-timing\":[\"hcid;desc=\\\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\\\",\n cfr;desc=\\\"9f80deb8e7c6dc3a-IAD\\\"\"],\n \"x-content-type-options\":[\"nosniff\"],\n \"x-hubspot-correlation-id\":[\"019e02d0-6fd8-7812-bdba-885b7ccb3ee3\"],\n \"Set-Cookie\":[\"__cf_bm=SIUrtdQgXVrik50pdqF6hZVYKhzTnQBidvMabeCtm0Y-1778163675-1.0.1.1-rI.ZggtDKxTge5zr8_2gbBfWMQQ.ufZEXDZyHz2mBUFdzdo2gTHEsOkXMSEShjK0hGYxNhUGM1ZoBpX7BcFZcHEjA7Cs_.SMUhUnd2nYjko; path=/; expires=Thu,\n 07-May-26 14:51:15 GMT; domain=.hubapi.com; HttpOnly; Secure; SameSite=None\"],\n \"Report-To\":[\"{\n\\\"endpoints\\\":[{\n\\\"url\\\":\\\"https:\\\\/\\\\/a.nel.cloudflare.com\\\\/report\\\\/v4?s=NYAlsVTP0fYm32qrSDjxYE4sd2RWRqiSp3wHsmdEgZlzoYdxI%2BIxVpHmsKn3O%2BKVA3mFIJ2m7YRECDGSM%2BW2IYTzo6FM4%2BdUIjURO8srzKSvJgZ%2BQ6R79arKQw3uHLlX\\\"}],\n\\\"group\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"NEL\":[\"{\n\\\"success_fraction\\\":0.01,\n\\\"report_to\\\":\\\"cf-nel\\\",\n\\\"max_age\\\":604800}\"],\n\"Server\":[\"cloudflare\"]}} {\n\"correlation_id\":\"95236535-ec98-4541-b92a-adfa73b69eab\",\n\"trace_id\":\"c7ab8365-903f-46d4-9403-0e5b551e3545\"}"}...
|
1
|
|
1636041
|
15029
|
accessibility
|
AXStaticText
|
Project
|
NULL
|
3
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
27
|
{"role_description":"text"}
|
0
|
|
1636042
|
15029
|
accessibility
|
AXButton
|
Project
|
NULL
|
3
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
28
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1636075
|
15030
|
accessibility
|
AXStaticText
|
Project
|
NULL
|
3
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
27
|
{"role_description":"text"}
|
0
|
|
1636100
|
15032
|
accessibility
|
AXTextArea
|
<?php
declare(strict_types=1);
namespace Jimi <?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
$rateLimiter->incrementRequestCount($activity->getCrm());
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}...
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
18
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":true,"is_selected":false,"role_description":"text entry area","value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Activity\\Import;\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\DTO\\ImportCall\\Call;\nuse Jiminny\\Component\\TranscriptionSummary\\Events\\TranscriptionAiSummaryReadyEvent;\nuse Jiminny\\Events\\Activities\\AiAutomation\\ActivityProspectAdded;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Participant;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\ActivityRepository;\nuse Jiminny\\Services\\Crm\\CrmObjectsResolver;\nuse Jiminny\\Services\\Crm\\ProspectSearchStrategyFactory;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Psr\\Log\\LoggerInterface;\n\nclass MatchCrmData implements ShouldQueue\n{\n use Dispatchable;\n use InteractsWithQueue;\n use Queueable;\n\n // AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.\n private const int MAX_DELAY = 43140;\n\n // Infrastructure allows max 3 retries (1 initial execution + 3 retries)\n public int $tries = 4;\n\n private Call $call;\n private int $activityId;\n private Team $team;\n private ServiceInterface $crmService;\n\n private LoggerInterface $logger;\n private array $logContext;\n\n public function __construct(Call $call, int $activityId)\n {\n $this->call = $call;\n $this->activityId = $activityId;\n\n $this->logContext = [\n 'activity_id' => $activityId,\n 'call_id' => $call->getCallId(),\n 'provider' => $call->getProvider(),\n ];\n\n $this->onQueue(Constants::QUEUE_DIALERS);\n }\n\n public function handle(\n CrmObjectsResolver $crmObjectsResolver,\n ProviderRegistry $providerRegistry,\n ProviderRateLimiter $rateLimiter,\n ActivityRepository $activityRepository,\n LoggerInterface $logger,\n Dispatcher $eventDispatcher,\n ): void {\n $this->logger = $logger;\n\n // Activity is already augmented with CRM data, no need to perform the same operation\n if ($this->call->isActivityUpdatedWithCrm()) {\n $this->logMessage('Skipping activity. Already updated with CRM data');\n\n return;\n }\n\n /** @var Activity $activity */\n $activity = $activityRepository->findById($this->activityId);\n\n try {\n $this->initialiseCrmService($activity, $providerRegistry);\n } catch (SocialAccountTokenInvalidException $exception) {\n $this->logMessage('Invalid token, retrying');\n $this->release(self::MAX_DELAY); // Try again tomorrow\n\n return;\n }\n\n $prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);\n if ($prospectSearchStrategy->ignoreCrmMatchData()) {\n // Ignore any associated opportunity\n $this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [\n 'activity_id' => $this->activityId,\n 'strategy' => get_class($prospectSearchStrategy),\n ]);\n\n return;\n }\n\n if (! $rateLimiter->canMakeRequest($activity->getCrm())) {\n $this->logMessage('Rate limit reached, retrying');\n $this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));\n\n return;\n }\n\n $this->logMessage('Resolving CRM objects');\n\n $crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);\n $rateLimiter->incrementRequestCount($activity->getCrm());\n\n if (empty($crmObjects)) {\n $this->logMessage('Could not resolve CRM objects, retrying');\n $this->release(3600);\n\n return;\n }\n\n [$lead, $account, $opportunity, $contact, $stage] = $crmObjects;\n\n $activity->update([\n 'lead_id' => $lead->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'account_id' => $account->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'stage_id' => $stage->id ?? null,\n ]);\n\n $activity->refresh();\n\n $eventDispatcher->dispatch(new ActivityProspectAdded(\n activity: $activity,\n eventSource: 'match-crm-data'\n ));\n\n if ($activity->getProspectName() !== null) {\n $activity->setTitleFromCallData($this->call);\n\n /** @var Participant $prospectParticipant */\n $prospectParticipant = $activity\n ->participants()\n ->where(function (Builder $query) use ($activity) {\n $query\n ->whereNull('user_id')\n ->orWhere('user_id', '!=', $activity->getUserId())\n ;\n })\n ->first()\n ;\n\n $activity->updateParticipantCrmData($crmObjects, $prospectParticipant);\n }\n\n $this->logMessage('Activity updated');\n\n $this->triggerSummaryPushIfReady($activity, $eventDispatcher);\n }\n\n private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void\n {\n if (! $activity->hasTranscriptionId()) {\n return;\n }\n\n if ($activity->hasProspectActivitySummaryLog()) {\n $this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');\n\n return;\n }\n\n $this->logMessage('Triggering summary push after CRM matching');\n $eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));\n }\n\n private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void\n {\n $this->team = $activity->getUser()->getTeam();\n $crmProviderName = $this->team->getCrmConfiguration()->getProviderName();\n\n $crmService = $providerRegistry->get($crmProviderName);\n $crmService->setUser($this->team->getOwner());\n $this->crmService = $crmService;\n\n $this->logContext['team'] = $this->team->getSlug();\n $this->logContext['team_id'] = $this->team->getId();\n }\n\n private function logMessage(string $message): void\n {\n $this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);\n }\n}"}...
|
1
|
|
1636109
|
15032
|
accessibility
|
AXStaticText
|
Project
|
NULL
|
3
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
27
|
{"role_description":"text"}
|
0
|
|
1637096
|
15042
|
accessibility
|
AXButton
|
Project: faVsco.js, menu
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
0
|
{"help_text":"~/jiminny/app"," {"help_text":"~/jiminny/app","is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637097
|
15042
|
accessibility
|
AXButton
|
JY-20725-handle-HS-search-rate-limit, menu
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
1
|
{"help_text":"Git Branch: JY-20725-han {"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637098
|
15042
|
accessibility
|
AXButton
|
Start Listening for PHP Debug Connections
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
2
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637099
|
15042
|
accessibility
|
AXButton
|
AskJiminnyReportActivityServiceTest
|
1637098
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
3
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637100
|
15042
|
accessibility
|
AXButton
|
Run 'AskJiminnyReportActivityServiceTest'
|
1637098
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
4
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637101
|
15042
|
accessibility
|
AXButton
|
Debug 'AskJiminnyReportActivityServiceTest' Debug 'AskJiminnyReportActivityServiceTest'...
|
1637098
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
5
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637102
|
15042
|
accessibility
|
AXButton
|
More Actions
|
1637098
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
6
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637103
|
15042
|
accessibility
|
AXButton
|
JetBrains AI
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
7
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637104
|
15042
|
accessibility
|
AXButton
|
Search Everywhere
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
8
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637105
|
15042
|
accessibility
|
AXButton
|
IDE and Project Settings
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
9
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637605
|
15047
|
accessibility
|
AXButton
|
Project: faVsco.js, menu
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
0
|
{"help_text":"~/jiminny/app"," {"help_text":"~/jiminny/app","is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637606
|
15047
|
accessibility
|
AXButton
|
JY-20725-handle-HS-search-rate-limit, menu
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
1
|
{"help_text":"Git Branch: JY-20725-han {"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637607
|
15047
|
accessibility
|
AXButton
|
Start Listening for PHP Debug Connections
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
2
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637608
|
15047
|
accessibility
|
AXButton
|
AskJiminnyReportActivityServiceTest
|
1637607
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
3
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637609
|
15047
|
accessibility
|
AXButton
|
Run 'AskJiminnyReportActivityServiceTest'
|
1637607
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
4
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637610
|
15047
|
accessibility
|
AXButton
|
Debug 'AskJiminnyReportActivityServiceTest' Debug 'AskJiminnyReportActivityServiceTest'...
|
1637607
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
5
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637611
|
15047
|
accessibility
|
AXButton
|
More Actions
|
1637607
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
6
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637612
|
15047
|
accessibility
|
AXButton
|
JetBrains AI
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
7
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637613
|
15047
|
accessibility
|
AXButton
|
Search Everywhere
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
8
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637614
|
15047
|
accessibility
|
AXButton
|
IDE and Project Settings
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
9
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637629
|
15048
|
accessibility
|
AXTextArea
|
<?php
declare(strict_types=1);
namespace Jimi <?php
declare(strict_types=1);
namespace Jiminny\Services\Crm\Hubspot\Pagination;
class PaginationState
{
public function __construct(
public int $requestCount = 0,
public int $totalRecords = 0,
public int $lastTokenCheck = 0,
public ?string $lastRecordId = null,
public float $startTime = 0.0,
public int $total = 0,
public int $offset = 0
) {
if ($this->startTime === 0.0) {
$this->startTime = microtime(true);
}
if ($this->lastTokenCheck === 0) {
$this->lastTokenCheck = now()->getTimestamp();
}
}
public function incrementRequestCount(): void
{
$this->requestCount++;
}
public function incrementTotalRecords(): void
{
$this->totalRecords++;
}
public function updateLastTokenCheck(): void
{
$this->lastTokenCheck = now()->getTimestamp();
}
public function setTotal(int $total): void
{
if ($this->total === 0) {
$this->total = $total;
}
}
public function updateLastRecordId(?string $lastRecordId): void
{
if ($lastRecordId !== null) {
$this->lastRecordId = $lastRecordId;
}
}
public function setOffset(int $offset): void
{
$this->offset = $offset;
}
public function shouldLogProgress(): bool
{
return $this->requestCount > 0 && $this->requestCount % PaginationConfig::PAGINATION_LOG_FREQUENCY === 0;
}
public function hasReachedSafetyLimit(): bool
{
return $this->requestCount >= PaginationConfig::LOOP_SAFETY_LIMIT;
}
public function shouldValidateToken(): bool
{
$currentTime = now()->getTimestamp();
return $this->requestCount > 0 && ($currentTime - $this->lastTokenCheck) >= PaginationConfig::TOKEN_CHECK_INTERVAL;
}
public function getElapsedSeconds(): float
{
return microtime(true) - $this->startTime;
}
}...
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
14
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"text entry area","value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Services\\Crm\\Hubspot\\Pagination;\n\nclass PaginationState\n{\n public function __construct(\n public int $requestCount = 0,\n public int $totalRecords = 0,\n public int $lastTokenCheck = 0,\n public ?string $lastRecordId = null,\n public float $startTime = 0.0,\n public int $total = 0,\n public int $offset = 0\n ) {\n if ($this->startTime === 0.0) {\n $this->startTime = microtime(true);\n }\n if ($this->lastTokenCheck === 0) {\n $this->lastTokenCheck = now()->getTimestamp();\n }\n }\n\n public function incrementRequestCount(): void\n {\n $this->requestCount++;\n }\n\n public function incrementTotalRecords(): void\n {\n $this->totalRecords++;\n }\n\n public function updateLastTokenCheck(): void\n {\n $this->lastTokenCheck = now()->getTimestamp();\n }\n\n public function setTotal(int $total): void\n {\n if ($this->total === 0) {\n $this->total = $total;\n }\n }\n\n public function updateLastRecordId(?string $lastRecordId): void\n {\n if ($lastRecordId !== null) {\n $this->lastRecordId = $lastRecordId;\n }\n }\n\n public function setOffset(int $offset): void\n {\n $this->offset = $offset;\n }\n\n public function shouldLogProgress(): bool\n {\n return $this->requestCount > 0 && $this->requestCount % PaginationConfig::PAGINATION_LOG_FREQUENCY === 0;\n }\n\n public function hasReachedSafetyLimit(): bool\n {\n return $this->requestCount >= PaginationConfig::LOOP_SAFETY_LIMIT;\n }\n\n public function shouldValidateToken(): bool\n {\n $currentTime = now()->getTimestamp();\n\n return $this->requestCount > 0 && ($currentTime - $this->lastTokenCheck) >= PaginationConfig::TOKEN_CHECK_INTERVAL;\n }\n\n public function getElapsedSeconds(): float\n {\n return microtime(true) - $this->startTime;\n }\n}"}...
|
1
|
|
1637638
|
15048
|
accessibility
|
AXStaticText
|
Project
|
NULL
|
3
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
23
|
{"role_description":"text"}
|
0
|
|
1637645
|
15050
|
accessibility
|
AXButton
|
Project: faVsco.js, menu
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
0
|
{"help_text":"~/jiminny/app"," {"help_text":"~/jiminny/app","is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637646
|
15050
|
accessibility
|
AXButton
|
JY-20725-handle-HS-search-rate-limit, menu
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
1
|
{"help_text":"Git Branch: JY-20725-han {"help_text":"Git Branch: JY-20725-handle-HS-search-rate-limit","is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637647
|
15050
|
accessibility
|
AXButton
|
Start Listening for PHP Debug Connections
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
2
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637648
|
15050
|
accessibility
|
AXButton
|
AskJiminnyReportActivityServiceTest
|
1637647
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
3
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637649
|
15050
|
accessibility
|
AXButton
|
Run 'AskJiminnyReportActivityServiceTest'
|
1637647
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
4
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637650
|
15050
|
accessibility
|
AXButton
|
Debug 'AskJiminnyReportActivityServiceTest' Debug 'AskJiminnyReportActivityServiceTest'...
|
1637647
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
5
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637651
|
15050
|
accessibility
|
AXButton
|
More Actions
|
1637647
|
6
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
6
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637652
|
15050
|
accessibility
|
AXButton
|
JetBrains AI
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
7
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637653
|
15050
|
accessibility
|
AXButton
|
Search Everywhere
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
8
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637654
|
15050
|
accessibility
|
AXButton
|
IDE and Project Settings
|
NULL
|
5
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
9
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637659
|
15050
|
accessibility
|
AXStaticText
|
7
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
14
|
{"role_description":"text"}
|
1
|
|
1637660
|
15050
|
accessibility
|
AXStaticText
|
2
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
15
|
{"role_description":"text"}
|
1
|
|
1637661
|
15050
|
accessibility
|
AXButton
|
Previous Highlighted Error
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
16
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637662
|
15050
|
accessibility
|
AXButton
|
Next Highlighted Error
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
17
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":false,"is_selected":false,"role_description":"button"}...
|
1
|
|
1637663
|
15050
|
accessibility
|
AXTextArea
|
<?php
declare(strict_types=1);
namespace Jimi <?php
declare(strict_types=1);
namespace Jiminny\Jobs\Activity\Import;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Events\Dispatcher;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Jiminny\Component\Queue\Constants;
use Jiminny\Component\Utility\Service\ProviderRateLimiter;
use Jiminny\Contracts\Services\Crm\ServiceInterface;
use Jiminny\DTO\ImportCall\Call;
use Jiminny\Component\TranscriptionSummary\Events\TranscriptionAiSummaryReadyEvent;
use Jiminny\Events\Activities\AiAutomation\ActivityProspectAdded;
use Jiminny\Exceptions\SocialAccountTokenInvalidException;
use Jiminny\Models\Activity;
use Jiminny\Models\Participant;
use Jiminny\Models\Team;
use Jiminny\Repositories\ActivityRepository;
use Jiminny\Services\Crm\CrmObjectsResolver;
use Jiminny\Services\Crm\ProspectSearchStrategyFactory;
use Jiminny\Services\Crm\ProviderRegistry;
use Psr\Log\LoggerInterface;
class MatchCrmData implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
// AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.
private const int MAX_DELAY = 43140;
// Infrastructure allows max 3 retries (1 initial execution + 3 retries)
public int $tries = 4;
private Call $call;
private int $activityId;
private Team $team;
private ServiceInterface $crmService;
private LoggerInterface $logger;
private array $logContext;
public function __construct(Call $call, int $activityId)
{
$this->call = $call;
$this->activityId = $activityId;
$this->logContext = [
'activity_id' => $activityId,
'call_id' => $call->getCallId(),
'provider' => $call->getProvider(),
];
$this->onQueue(Constants::QUEUE_DIALERS);
}
public function handle(
CrmObjectsResolver $crmObjectsResolver,
ProviderRegistry $providerRegistry,
ProviderRateLimiter $rateLimiter,
ActivityRepository $activityRepository,
LoggerInterface $logger,
Dispatcher $eventDispatcher,
): void {
$this->logger = $logger;
// Activity is already augmented with CRM data, no need to perform the same operation
if ($this->call->isActivityUpdatedWithCrm()) {
$this->logMessage('Skipping activity. Already updated with CRM data');
return;
}
/** @var Activity $activity */
$activity = $activityRepository->findById($this->activityId);
try {
$this->initialiseCrmService($activity, $providerRegistry);
} catch (SocialAccountTokenInvalidException $exception) {
$this->logMessage('Invalid token, retrying');
$this->release(self::MAX_DELAY); // Try again tomorrow
return;
}
$prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);
if ($prospectSearchStrategy->ignoreCrmMatchData()) {
// Ignore any associated opportunity
$this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [
'activity_id' => $this->activityId,
'strategy' => get_class($prospectSearchStrategy),
]);
return;
}
if (! $rateLimiter->canMakeRequest($activity->getCrm())) {
$this->logMessage('Rate limit reached, retrying');
$this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));
return;
}
$this->logMessage('Resolving CRM objects');
$crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);
$rateLimiter->incrementRequestCount($activity->getCrm());
if (empty($crmObjects)) {
$this->logMessage('Could not resolve CRM objects, retrying');
$this->release(3600);
return;
}
[$lead, $account, $opportunity, $contact, $stage] = $crmObjects;
$activity->update([
'lead_id' => $lead->id ?? null,
'contact_id' => $contact->id ?? null,
'account_id' => $account->id ?? null,
'opportunity_id' => $opportunity->id ?? null,
'stage_id' => $stage->id ?? null,
]);
$activity->refresh();
$eventDispatcher->dispatch(new ActivityProspectAdded(
activity: $activity,
eventSource: 'match-crm-data'
));
if ($activity->getProspectName() !== null) {
$activity->setTitleFromCallData($this->call);
/** @var Participant $prospectParticipant */
$prospectParticipant = $activity
->participants()
->where(function (Builder $query) use ($activity) {
$query
->whereNull('user_id')
->orWhere('user_id', '!=', $activity->getUserId())
;
})
->first()
;
$activity->updateParticipantCrmData($crmObjects, $prospectParticipant);
}
$this->logMessage('Activity updated');
$this->triggerSummaryPushIfReady($activity, $eventDispatcher);
}
private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void
{
if (! $activity->hasTranscriptionId()) {
return;
}
if ($activity->hasProspectActivitySummaryLog()) {
$this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');
return;
}
$this->logMessage('Triggering summary push after CRM matching');
$eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));
}
private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void
{
$this->team = $activity->getUser()->getTeam();
$crmProviderName = $this->team->getCrmConfiguration()->getProviderName();
$crmService = $providerRegistry->get($crmProviderName);
$crmService->setUser($this->team->getOwner());
$this->crmService = $crmService;
$this->logContext['team'] = $this->team->getSlug();
$this->logContext['team_id'] = $this->team->getId();
}
private function logMessage(string $message): void
{
$this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);
}
}...
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
18
|
{"is_enabled":true,"is_expanded":f {"is_enabled":true,"is_expanded":false,"is_focused":true,"is_selected":false,"role_description":"text entry area","value":"<?php\n\ndeclare(strict_types=1);\n\nnamespace Jiminny\\Jobs\\Activity\\Import;\n\nuse Illuminate\\Bus\\Queueable;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Database\\Eloquent\\Builder;\nuse Illuminate\\Events\\Dispatcher;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Jiminny\\Component\\Queue\\Constants;\nuse Jiminny\\Component\\Utility\\Service\\ProviderRateLimiter;\nuse Jiminny\\Contracts\\Services\\Crm\\ServiceInterface;\nuse Jiminny\\DTO\\ImportCall\\Call;\nuse Jiminny\\Component\\TranscriptionSummary\\Events\\TranscriptionAiSummaryReadyEvent;\nuse Jiminny\\Events\\Activities\\AiAutomation\\ActivityProspectAdded;\nuse Jiminny\\Exceptions\\SocialAccountTokenInvalidException;\nuse Jiminny\\Models\\Activity;\nuse Jiminny\\Models\\Participant;\nuse Jiminny\\Models\\Team;\nuse Jiminny\\Repositories\\ActivityRepository;\nuse Jiminny\\Services\\Crm\\CrmObjectsResolver;\nuse Jiminny\\Services\\Crm\\ProspectSearchStrategyFactory;\nuse Jiminny\\Services\\Crm\\ProviderRegistry;\nuse Psr\\Log\\LoggerInterface;\n\nclass MatchCrmData implements ShouldQueue\n{\n use Dispatchable;\n use InteractsWithQueue;\n use Queueable;\n\n // AWS visibility timeout allows a maximum of 12 hours. This is 1 minute less.\n private const int MAX_DELAY = 43140;\n\n // Infrastructure allows max 3 retries (1 initial execution + 3 retries)\n public int $tries = 4;\n\n private Call $call;\n private int $activityId;\n private Team $team;\n private ServiceInterface $crmService;\n\n private LoggerInterface $logger;\n private array $logContext;\n\n public function __construct(Call $call, int $activityId)\n {\n $this->call = $call;\n $this->activityId = $activityId;\n\n $this->logContext = [\n 'activity_id' => $activityId,\n 'call_id' => $call->getCallId(),\n 'provider' => $call->getProvider(),\n ];\n\n $this->onQueue(Constants::QUEUE_DIALERS);\n }\n\n public function handle(\n CrmObjectsResolver $crmObjectsResolver,\n ProviderRegistry $providerRegistry,\n ProviderRateLimiter $rateLimiter,\n ActivityRepository $activityRepository,\n LoggerInterface $logger,\n Dispatcher $eventDispatcher,\n ): void {\n $this->logger = $logger;\n\n // Activity is already augmented with CRM data, no need to perform the same operation\n if ($this->call->isActivityUpdatedWithCrm()) {\n $this->logMessage('Skipping activity. Already updated with CRM data');\n\n return;\n }\n\n /** @var Activity $activity */\n $activity = $activityRepository->findById($this->activityId);\n\n try {\n $this->initialiseCrmService($activity, $providerRegistry);\n } catch (SocialAccountTokenInvalidException $exception) {\n $this->logMessage('Invalid token, retrying');\n $this->release(self::MAX_DELAY); // Try again tomorrow\n\n return;\n }\n\n $prospectSearchStrategy = ProspectSearchStrategyFactory::match($this->team);\n if ($prospectSearchStrategy->ignoreCrmMatchData()) {\n // Ignore any associated opportunity\n $this->logger->info('[MatchCrmData] Ignoring crm data because of prospect strategy', [\n 'activity_id' => $this->activityId,\n 'strategy' => get_class($prospectSearchStrategy),\n ]);\n\n return;\n }\n\n if (! $rateLimiter->canMakeRequest($activity->getCrm())) {\n $this->logMessage('Rate limit reached, retrying');\n $this->release($rateLimiter->requestAvailableIn($activity->getCrm()) + random_int(1, 60));\n\n return;\n }\n\n $this->logMessage('Resolving CRM objects');\n\n $crmObjects = $crmObjectsResolver->resolveFromCall($this->crmService, $this->call);\n $rateLimiter->incrementRequestCount($activity->getCrm());\n\n if (empty($crmObjects)) {\n $this->logMessage('Could not resolve CRM objects, retrying');\n $this->release(3600);\n\n return;\n }\n\n [$lead, $account, $opportunity, $contact, $stage] = $crmObjects;\n\n $activity->update([\n 'lead_id' => $lead->id ?? null,\n 'contact_id' => $contact->id ?? null,\n 'account_id' => $account->id ?? null,\n 'opportunity_id' => $opportunity->id ?? null,\n 'stage_id' => $stage->id ?? null,\n ]);\n\n $activity->refresh();\n\n $eventDispatcher->dispatch(new ActivityProspectAdded(\n activity: $activity,\n eventSource: 'match-crm-data'\n ));\n\n if ($activity->getProspectName() !== null) {\n $activity->setTitleFromCallData($this->call);\n\n /** @var Participant $prospectParticipant */\n $prospectParticipant = $activity\n ->participants()\n ->where(function (Builder $query) use ($activity) {\n $query\n ->whereNull('user_id')\n ->orWhere('user_id', '!=', $activity->getUserId())\n ;\n })\n ->first()\n ;\n\n $activity->updateParticipantCrmData($crmObjects, $prospectParticipant);\n }\n\n $this->logMessage('Activity updated');\n\n $this->triggerSummaryPushIfReady($activity, $eventDispatcher);\n }\n\n private function triggerSummaryPushIfReady(Activity $activity, Dispatcher $eventDispatcher): void\n {\n if (! $activity->hasTranscriptionId()) {\n return;\n }\n\n if ($activity->hasProspectActivitySummaryLog()) {\n $this->logMessage('Summary already sent to prospect, skipping summary push after CRM matching');\n\n return;\n }\n\n $this->logMessage('Triggering summary push after CRM matching');\n $eventDispatcher->dispatch(new TranscriptionAiSummaryReadyEvent($activity->getUuid()));\n }\n\n private function initialiseCrmService(Activity $activity, ProviderRegistry $providerRegistry): void\n {\n $this->team = $activity->getUser()->getTeam();\n $crmProviderName = $this->team->getCrmConfiguration()->getProviderName();\n\n $crmService = $providerRegistry->get($crmProviderName);\n $crmService->setUser($this->team->getOwner());\n $this->crmService = $crmService;\n\n $this->logContext['team'] = $this->team->getSlug();\n $this->logContext['team_id'] = $this->team->getId();\n }\n\n private function logMessage(string $message): void\n {\n $this->logger->info(sprintf('[MatchCrmData] %s', $message), $this->logContext);\n }\n}"}...
|
1
|
|
1637668
|
15050
|
accessibility
|
AXStaticText
|
19
|
NULL
|
4
|
NULL
|
NULL
|
NULL
|
NULL
|
NULL
|
23
|
{"role_description":"text"}
|
1
|